4

それで、Postgresql での外部キー制約の処理について混乱しています。(バージョン 8.4.4、その価値はあります)。

いくつかのテーブルがあり、以下に穏やかに匿名化されています。

device:
   (id, blah, blah, blah, blah, blah x 50)…
   primary key on id
   whooooole bunch of other junk

device_foo:
   (id, device_id, left, right)
   Foreign key (device_id) references device(id) on delete cascade;
   primary key on id
   btree index on 'left' and 'right'

そこで、いくつかのクエリを実行するために 2 つのデータベース ウィンドウを用意しました。

db1>  begin; lock table device in exclusive mode;
db2>  begin; update device_foo set left = left + 1;

db2 接続がブロックされます。

device_stuff の「左」列の更新がデバイス テーブルのアクティビティの影響を受けるというのは奇妙に思えます。しかし、そうです。実際、db1 に戻ると、次のようになります。

db1> select * from device_stuff for update;
          *** deadlock occurs ***

pgsql ログには次の内容があります。

blah blah blah deadlock blah.
CONTEXT: SQL statement "SELECT 1 FROM ONLY "public"."device" x WHERE "id" OPERATOR(pg_catalog.=) $1 FOR SHARE OF X: update device_foo set left = left + 1;

私には 2 つの問題があると思います。1 つ目は、この種のロックが発生する正確なメカニズムを理解していないことです。pg_locks をクエリして、ステートメントが呼び出すロックの種類を確認するための便利なクエリがいくつかありますが、update device_fooコマンドを単独で実行したときに、この特定の種類のロックを観察することはできませんでした。(おそらく、私は何か間違ったことをしているのかもしれません。) また、外部キー制約チェックのロック取得動作に関するドキュメントも見つかりません。私が持っているのはログメッセージだけです。このことから、行を変更すると、外部キーが設定されているすべてのテーブルで更新ロックが取得されると推測できますか?

2番目の問題は、そのようにならないようにする方法を見つけたいということです。実際のアプリケーションで時々デッドロックが発生します。device_fooデバイス テーブルで大きなロックを取得することなく、すべての行に影響を与える大きな更新ステートメントを実行できるようにしたいと考えています。(テーブルでは多くのアクセスが行われており、device取得するのにコストのかかるロックのようなものです。)

4

2 に答える 2

3

このステートメントlock table device in exclusive modeは、テーブルに対して非常に制限的なロックを取得します ( 「排他モード」 )。親テーブルへの外部キーを持つテーブルを変更すると、親テーブルにかなり無害な共有ロックがかかります (たとえば、テーブルを参照する行が更新される可能性がある間は、テーブルを切り捨てることはできません)。

実際、今試してみると、あなたのロック動作を再現できません (8.4.4 ではそうです)。やった:

create table device(device_id serial primary key, value text not null);
create table device_foo(device_foo_id serial primary key, device_id int not null references device(device_id) on delete cascade, value text not null);
insert into device(value) values('FOO'),('BAR'),('QUUX');
insert into device_foo(device_id, value) select device_id, v.value from (values('mumble'),('grumble'),('fumble')) v(value), device;

そして、2つの同時接続で次のことを行いました:

<1>=# begin; lock table device in exclusive mode;
<2>=# begin; update device_foo set value = value || 'x';

これはあなたがしていることと同等のように見えますが、2番目のセッションがロックされていません.予想どおり、すぐに「UPDATE 9」が表示されます. ご想像のとおり、ブロックへの挿入と、列device_fooを設定する update ステートメントも同様です。db2 セッションの db1 セッションからdevice_idの ExclusiveLock を確認できます。pg_locksまた、「select * from device for share」を実行するとブロックされます。これは、デッドロック エラーで表示されているステートメントです。また、db2 が device_foo の device_id 列を更新しようとしてブロックされているときに、db1 接続から「select * from device_foo for update」を実行しても、デッドロックは発生しません。

行を更新すると、その行はロックされているとマークされますが、そのロックは pg_locks には表示されません。また、テーブルのロックを取得して、テーブルの行の 1 つが更新されている間にテーブルを削除/切り捨て/再インデックス化しようとする人をロックアウトします。

同時更新に対してテーブルをロックするにdeviceは、より厳密でないロック モードが必要になる場合があります。マニュアルでは、この種のアクティビティに対して「行を排他的に共有する」ことを提案しています。これは「排他的」から 1 つ下のレベルですが、「select ... for share」ステートメントと互換性があります。

未解決の問題は、「select ... for share」クエリを発行しているのは何ですか? :-S 外部キーの整合性を主張するためのステートメントのように見えますが、再現できません。

于 2010-06-14T22:05:53.403 に答える
0

テーブルを排他モードでロックするということは、どのプロセスもそのテーブルを読み取ることができないことを意味し、外部キーをチェックするにはテーブルデバイスを読み取る必要があります。

于 2010-06-14T20:21:31.157 に答える