10

postgres でのロックのメカニズムをよりよく理解したいと思います。

ツリーがリンゴを持つことができるとしましょう(リンゴテーブルの外部キーを介して)。更新ロックのためにツリーを選択すると、リンゴが取得されるようです。ただし、他の誰かが既にこのリンゴをロックしている場合でも、操作はブロックされません。

なぜそうなのですか?

ps 「select for update」を削除するよう提案しないでください。

シナリオ

Transaction 1      Transaction 2
BEGIN              .
update apple;      .
.                  BEGIN
.                  select tree for update;
.                  update apple;
.                  --halts because of the other transaction locking an apple
update apple;      .
-- deadlock        .
                   COMMIT
                   --transaction succeeds

コード

postgres で試してみたい場合は、コピー/貼り付けできるコードを次に示します。

次のデータベーススキーマがあります

CREATE TABLE trees (
    id       integer primary key
);

create table apples (
    id       integer primary key,
    tree_id  integer references trees(id)
);

と非常に単純なデータ

insert into trees values(1);
insert into apples values(1,1);

2 つの単純なトランザクションがあります。1 つはリンゴの更新、2 番目はツリーのロックとリンゴの更新です。

BEGIN;
    UPDATE apples SET id = id WHERE id = 1;
    -- run second transaction in paralell
    UPDATE apples SET id = id WHERE id = 1;
COMMIT;

BEGIN;
    SELECT id FROM trees WHERE id = 1 FOR UPDATE;
    UPDATE apples SET id = id WHERE id = 1;
COMMIT;

それらを実行すると、最初のトランザクションの 2 回目の更新でデッドロックが発生します。

ERROR:  deadlock detected
DETAIL:  Process 81122 waits for ShareLock on transaction 227154; blocked by process 81100.
Process 81100 waits for ShareLock on transaction 227153; blocked by process 81122.
CONTEXT:  SQL statement "SELECT 1 FROM ONLY "public"."trees" x WHERE "id" OPERATOR(pg_catalog.=) $1 FOR SHARE OF x"
4

2 に答える 2

13

大まかな推測: 実装の詳細に関連する問題が発生しています...

具体的には、select tree for updateステートメントはツリーの排他ロックを取得します。そしてupdate applesステートメントは、関連するリンゴの排他ロックを取得します。

リンゴで更新を実行すると、行ごとのトリガーに関連する Postgres の外部キーが起動し、 が存在することを確認しtree_idます。それらの正確な名前を頭の中で思い出すことはできませんが、それらはカタログにあり、ドキュメントにはそれらを明示的または暗黙的に参照する断片があります。

create constraint trigger ... on ... from ...

http://www.postgresql.org/docs/current/static/sql-createtrigger.html

いずれにせよ、これらのトリガーは次のようなものを実行します。

select exists (select 1 from trees where id = 1);

そこにあなたの問題があります: による排他的アクセスはselect for update、トランザクション 2 がリンゴの更新ステートメントを完了するためにツリーのロックを解放するのを待ちますが、トランザクション 2 はロックを取得するためにトランザクション 1 が完了するのを待っています。リンゴで更新ステートメントを開始するように、リンゴで。

その結果、Postgres はデッドロックに陥ります。

于 2013-09-02T10:32:11.143 に答える
-3

トランザクションの全期間にわたってインデックス ロックが保持されていないようです。主な問題は、トランザクション 1 が同じUPDATEことを 2 回実行していることですが、2 回目を実行するにはさらに多くのロックを取得する必要があることだと思いますUPDATE

docsによると、インデックス ロックは短時間しか保持されません。データ ロックとは異なり、トランザクションが完了するまで保持されません。タイムラインを詳しく見てみましょう。

トランザクション 1 が最初のUPDATE. これにより、 の行に対する行レベルのロックが取得されapplesます。操作中に、 のインデックスに対するロックも取得しますtrees。トランザクションはまだコミットされていないため、行レベルのデータ ロックはトランザクション 1 によって引き続き保持されます。ただし、インデックス ロックtreesはすぐに解放されます。Postgres がすべてのインデックス タイプに対してこれを行う理由がわかりません。

トランザクション 2 が発生しtrees、更新のためにロックされます。これにより、データとインデックスの両方がロックされます。トランザクション 1 が既にインデックス ロックを解放しているため、これはブロックされません。今回は、両方のロックがトランザクションの終了まで保持されます。このインデックス ロックが保持されているのに、他のロックが解放されている理由がわかりません。

トランザクション 1 が戻ってきて、再試行しUPDATEます。ロックオンapplesは既に付いているので問題ありません。ただし、のロックはtrees、トランザクション 2 が既に持っているため、ブロックされます。

トランザクション 2 に を追加するUPDATEと、トランザクション 1 で待機するようになり、デッドロックが発生します。

編集:

Postgresがインストールされたので、これをさらに調査するために戻ってきました。それは実際には本当に奇妙です。pg_locksトランザクション2をコミットした後に見ました。

トランザクション 1 には次のロックがあります。

  • apples_pkey と apples の RowExclusive
  • その transactionid と virtualxid で排他的

トランザクション 2 には、次のロック (およびその他の無関係なロックが多数あります) があります。

  • trees_pkey の AccessShare
  • ツリー上の RowShare
  • その transactionid と virtualxid で排他的
  • apples_pkey と apples の RowExclusive
  • リンゴのタプルに排他的

トランザクション 2 も、トランザクション 1 の共有ロックの取得を待機しています。

興味深いことに、2 つのトランザクションが同じテーブルに対して RowExclusive ロックを保持できます。ただし、排他ロックは共有と競合するため、トランザクション 2 はトランザクション 1 のトランザクション ID を待機しています。ドキュメントでは、他のトランザクションを待機する方法としてトランザクション ロックについて言及しています。したがって、トランザクション 2 はコミットされているものの、まだトランザクション 1 を待っているように見えます。

トランザクション 1 が続行すると、トランザクション 2 で共有ロックを取得しようとするため、デッドロックが発生します。トランザクション 2 で共有ロックを取得する必要があるのはなぜですか? それについてはよくわかりません。ドキュメントは、この情報が では利用できないことを示唆していますpg_locks。これは MVCC に関連していると思いますが、私にはまだ謎です。

于 2013-09-03T00:49:08.117 に答える