web-dev-qa-db-ja.com

アトミック更新.. PostgresでのSELECT

私は一種のキューイングメカニズムを構築しています。処理が必要なデータ行とステータスフラグがあります。それを管理するためにupdate .. returning句を使用しています:

UPDATE stuff
SET computed = 'working'
WHERE id = (SELECT id from STUFF WHERE computed IS NULL LIMIT 1)
RETURNING * 

ネストされた選択部分は更新と同じロックですか、またはここで競合状態がありますか?その場合、内部選択はselect for updateである必要がありますか?

47
kolosy

アーウィンの提案はおそらく正しい動作を得るための最も簡単な方法です(SQLSTATEで例外が発生した場合にトランザクションを再試行する限り) 40001)の性質により、キューイングアプリケーションは、PostgreSQLのSERIALIZABLEトランザクションの実装よりも、リクエストをブロックしてキューで順番をとるチャンスよりもうまく機能する傾向があります。 「衝突の可能性について。

質問の例のクエリは、現状ではデフォルトのREAD COMMITTEDトランザクション分離レベルで、2つ(またはそれ以上)の同時接続が両方ともキューから同じ行を「要求」することを許可します。これは何が起こるでしょう:

  • T1が開始し、UPDATEフェーズで行をロックするまで到達します。
  • T2は実行時にT1と重複し、その行を更新しようとします。 T1のCOMMITまたはROLLBACKの保留をブロックします。
  • T1はコミットし、行を正常に「要求」しました。
  • T2は行を更新しようとし、T1がすでに持っていることを見つけ、行の新しいバージョンを探し、選択基準(idが一致するだけ)をまだ満たしていることを見つけ、さらに行。

正しく動作するように変更できます(サブクエリでFOR UPDATE句を許可するバージョンのPostgreSQLを使用している場合)。 idを選択するサブクエリの最後にFOR UPDATEを追加するだけで、次のようになります。

  • T1が開始し、IDをselectingする前に行をロックします。
  • T2は、T1のCOMMITまたはROLLBACKを保留して、IDを選択しようとしている間、実行時間でT1とオーバーラップしてブロックします。
  • T1はコミットし、行を正常に「要求」しました。
  • T2がその行をreadしてIDを確認できるようになるまでに、そのIDが要求されていることがわかり、次の利用可能なIDを見つけます。

REPEATABLE READまたはSERIALIZABLEトランザクション分離レベルでは、書き込みの競合によりエラーがスローされ、SQLSTATEに基づいてシリアル化の失敗をキャッチして判別し、再試行できます。

一般にSERIALIZABLEトランザクションが必要であるが、キューイング領域での再試行を避けたい場合は、 アドバイザリロック を使用してそれを実現できる場合があります。

35
kgrittn