6

postgresで次のステートメントを実行すると

update table set col = 1 where col = 2

デフォルトのREAD COMMITTED分離レベルでは、複数の同時セッションから、次のことが保証されますか?

  1. 単一の一致の場合、1つのスレッドのみが1のROWCOUNTを取得します(つまり、1つのスレッドのみが書き込みます)
  2. 1つのスレッドのみがROWCOUNT>0を取得するマルチマッチの場合(1つのスレッドのみがバッチを書き込むことを意味します)
4

1 に答える 1

13

あなたが述べた保証はこの単純な場合に適用されますが、必ずしも少し複雑なクエリには適用されません。例については、回答の最後を参照してください。

単純なケース

col1が一意である、値が1つだけである、または順序が安定しているため、すべてUPDATEが同じ行に同じ順序で一致するとします。

このクエリで何が起こるかというと、スレッドはcol = 2の行を見つけ、すべてがそのタプルの書き込みロックを取得しようとします。そのうちの1つが成功します。他のスレッドは、最初のスレッドのトランザクションがコミットするのを待つのをブロックします。

その最初のtxは、書き込み、コミット、および行数1を返します。コミットにより、ロックが解放されます。

他のtxは、再びロックを取得しようとします。一つずつ成功します。各トランザクションは、次のプロセスを経ます。

  • 競合するタプルの書き込みロックを取得します。
  • WHERE col=2ロックを取得した後、状態を再確認してください。
  • 再チェックすると、条件が一致しなくなったことが示されるため、UPDATEはその行をスキップします。
  • には他のUPDATE行がないため、更新されたゼロ行が報告されます。
  • コミットし、ロックを解除して次のtxがロックを取得しようとします。

この単純なケースでは、行レベルのロックと条件の再チェックにより、更新が効果的にシリアル化されます。より複雑なケースでは、それほど多くはありません。

これを簡単に示すことができます。たとえば、4つのpsqlセッションを開きます。BEGIN; LOCK TABLE test;最初に、テーブルを*でロックします。残りのセッションでは同じものを実行UPDATEします-テーブルレベルのロックでブロックします。COMMIT次に、最初のセッションを実行してロックを解除します。彼らが競争するのを見てください。1つだけが1の行数を報告し、他は0を報告します。これは簡単に自動化され、繰り返しとより多くの接続/スレッドへのスケールアップのためにスクリプト化されます。

詳細については、 PostgreSQLの同時実行の問題の11ページにある同時書き込みのルールを読んでから、そのプレゼンテーションの残りの部分を読んでください。

そして、col1が一意でない場合はどうなりますか?

Kevinがコメントで指摘したように、col一意でないために複数の行に一致する可能性がある場合、の実行がUPDATE異なると順序が異なる可能性があります。これは、異なるプランを選択した場合(たとえば、1つが経由で、もう1つが直接である場合、またはGUCをいじっている場合)、またはすべてが使用するプランが不安定な種類の等しい値を使用している場合に発生する可能性がPREPAREありEXECUTEますenable_。行を異なる順序で取得する場合、tx1は1つのタプルをロックし、tx2は別のタプルをロックします。次に、それぞれが互いのすでにロックされているタプルをロックしようとします。PostgreSQLはデッドロック例外でそれらの1つを中止します。これは、すべてのデータベースコードが常にトランザクションを再試行できるように準備する必要があるもう1つの理由です。

同時実行が常に同じ行を同じ順序で取得するように注意する場合UPDATEでも、回答の最初の部分で説明されている動作に依存できます。

苛立たしいことに、PostgreSQLは提供していないUPDATE ... ORDER BYので、更新が常に同じ行を同じ順序で選択することを保証することは、あなたが望むほど単純ではありません。SELECT ... FOR UPDATE ... ORDER BY多くの場合、その後に別のが続くのがUPDATE最も安全です。

より複雑なクエリ、キューイングシステム

複数のタプルを含む複数のフェーズ、または等式以外の条件でクエリを実行している場合は、シリアル実行の結果とは異なる驚くべき結果が得られる可能性があります。特に、次のようなものの同時実行:

UPDATE test SET col = 1 WHERE col = (SELECT t.col FROM test t ORDER BY t.col LIMIT 1);

または、単純な「キュー」システムを構築するための他の努力は、期待どおりに機能しません。詳細については、並行性に関するPostgreSQLドキュメントこのプレゼンテーションを参照してください。

データベースに裏打ちされたワークキューが必要な場合は、驚くほど複雑なコーナーケースをすべて処理する十分にテストされたソリューションがあります。最も人気のあるものの1つはPgQです。このトピックに関する有用なPgConペーパーがあり、「postgresqlqueue」のGoogle検索は有用な結果でいっぱいです。


* ところで、代わりに、タプルの書き込みロックを取得するためにLOCK TABLE使用できます。SELECT 1 FROM test WHERE col = 2 FOR UPDATE;これは、それに対する更新をブロックしますが、他のタプルへの書き込みをブロックしたり、読み取りをブロックしたりすることはありません。これにより、さまざまな種類の同時実行の問題をシミュレートできます。

于 2012-08-11T13:29:57.620 に答える