あなたが述べた保証はこの単純な場合に適用されますが、必ずしも少し複雑なクエリには適用されません。例については、回答の最後を参照してください。
単純なケース
col1が一意である、値が「2」である、または順序が安定していると仮定すると、すべてのUPDATE
同じ行を同じ順序で一致させます:
このクエリで何が起こるかというと、スレッドはcol =2の行を見つけ、すべてがそのタプルの書き込みロックを取得しようとします。そのうちの1つが成功します。他のスレッドは、最初のスレッドのトランザクションがコミットするのを待つのをブロックします。
その最初のtxは、書き込み、コミット、および行数1を返します。コミットにより、ロックが解放されます。
他のtxは、再びロックを取得しようとします。一つずつ成功します。各トランザクションは、次のプロセスを経ます。
- 競合するタプルの書き込みロックを取得します。
-
WHERE col=2
を再確認します ロックを取得した後の状態。 - 再チェックにより、条件が一致しなくなったことが示されるため、
UPDATE
その行をスキップします。 -
UPDATE
他に行がないため、更新された行はゼロと報告されます。 - コミットし、ロックを解除して次のtxがロックを取得しようとします。
この単純なケースでは、行レベルのロックと条件の再チェックにより、更新が効果的にシリアル化されます。より複雑なケースでは、それほど多くはありません。
これを簡単に示すことができます。たとえば、4つのpsqlセッションを開きます。最初に、BEGIN; LOCK TABLE test;
。残りのセッションでは、同じUPDATE
を実行します s-テーブルレベルのロックでブロックします。 COMMIT
でロックを解除します 最初のセッションを行います。彼らが競争するのを見てください。 1つだけが1の行数を報告し、他は0を報告します。これは簡単に自動化され、繰り返してスクリプトを作成して、より多くの接続/スレッドにスケールアップできます。
詳細については、同時書き込みのルール をご覧ください。 、PostgreSQLの同時実行性の問題 の11ページ -そして、そのプレゼンテーションの残りの部分を読みます。
そしてcol1が一意でない場合は?
ケビンがコメントで述べたように、col
は一意ではないため、複数の行に一致してから、UPDATE
の実行が異なる可能性があります 異なる注文を取得する可能性があります。これは、別のプランを選択した場合に発生する可能性があります(たとえば、1つはPREPARE
経由です およびEXECUTE
もう1つは直接的なものであるか、enable_
をいじっています。 GUC)、またはそれらがすべて使用するプランが不安定な種類の等しい値を使用する場合。行を異なる順序で取得する場合、tx1は1つのタプルをロックし、tx2は別のタプルをロックし、それぞれが互いのすでにロックされているタプルをロックしようとします。 PostgreSQLは、デッドロック例外を除いて、そのうちの1つを中止します。これが、すべてのもう1つの理由です。 データベースコードは常に トランザクションを再試行する準備をしてください。
同時UPDATE
を確認するように注意している場合 sは常に同じ行を同じ順序で取得しますが、回答の最初の部分で説明されている動作に依拠することができます。
苛立たしいことに、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
です。 。便利な
LOCK TABLE
の代わりにBTW SELECT 1 FROM test WHERE col = 2 FOR UPDATE;
を使用できます タプルの書き込みロックを取得します。これは、それに対する更新をブロックしますが、他のタプルへの書き込みをブロックしたり、読み取りをブロックしたりすることはありません。これにより、さまざまな種類の同時実行の問題をシミュレートできます。