web-dev-qa-db-ja.com

単一のUPDATEクエリでデッドロックが発生するのはなぜですか?

このようなコードを並行して実行する2つのプロセスがあります。

begin;
update foos set unread=false where owner_id=123 and unread=true;
commit;

これによりデッドロックが発生します。

デッドロックの原因についての私の理解は、 この質問 で説明されているシナリオに似ており、 "織り交ぜられた" UPDATEステートメントが2つの異なる行を異なる順序で更新します。単一のUPDATEステートメントでデッドロックが発生する方法を理解できません。開発環境で2つの並列PSQLセッションを使用してデッドロックシナリオを複製することができません。なぜそれを複製できないのかについての私の推測:

  1. デッドロックエラーを作成するコードを誤解しており、実際には各トランザクションに複数のUPDATEステートメントがあります
  2. 「織り交ぜられた」側面は起こっていますが、複数行をカバーするUPDATEステートメントの「内部」にあるため、複製するのは困難です。

この単一のUPDATEがデッドロックを作成することは可能ですか?

2
John Bachir

ステートメントはいくつかの行を変更します。これらの各行は、更新時にロックされます。

並行トランザクションのステートメントがこれらの行の1つをすでにロックしているため、UPDATEがブロックされている可能性があります。その後、並行トランザクションがUPDATEがすでにロックしている行の1つをロックしようとすると、デッドロックが発生します。

2
Laurenz Albe

ローレンツは説明しました デッドロックにつながる可能性のあるメカニズムであり、ケビンによるより詳細な説明へのリンクをすでに自分で含めています。

デッドロックを複製する方法をステップごとに説明します。SELECT .. FOR UPDATEと同じように、プレーンUPDATEでも機能します。

今、どのように問題を回避する
実質的なシェアまたはすべてのテーブルを更新する場合-そして、余裕があれば-ただ テーブルを書き込みロックする です。通常、これは進むべき道ではありません。それ以外の場合、3つの異なるアプローチ:

1.一貫した注文

マニュアル はデッドロックの章でこのアドバイスを持っています:

デッドロックに対する最善の防御策は、データベースを使用するすべてのアプリケーションが複数のオブジェクトのロックを一貫した順序で確実に取得することで、デッドロックを回避することです。

UPDATEno ORDER BYがまだ存在する理由がわかりません。しかし、それは私たちが協力しなければならないことです。代わりに、同じトランザクションでSELECT ... FOR UPDATEを使用して行をロックします- 前の質問 が示すように、すでに試行したように。重要な決定論ORDER BYを忘れただけです。

BEGIN;
SELECT FROM foos WHERE owner_id = 123 AND unread
ORDER  BY ??? -- any deterministic order, PK would be an obvious candidate
FOR    UPDATE;

UPDATE foos SET unread = false WHERE owner_id = 123 AND unread;
END;

明らかに、all潜在的に競合するトランザクションは、同じ順序でロックを取得する必要があります。

2.ロックされた行をスキップ

ロックされていない行のみを処理します。

BEGIN;
SELECT FROM foos WHERE owner_id = 123 AND unread
-- ORDER BY ???  -- optional in this case
FOR    UPDATE SKIP LOCKED;

UPDATE foos SET unread = false WHERE owner_id = 123 AND unread;
END;

スキップされた行が、同じことをしている競合するトランザクションによって処理されたことが確実な場合は、ここで完了です。 (本気ですか?)
それ以外の場合は、確認のためにチェックを続けます。

SELECT EXISTS (SELECT FROM foos WHERE owner_id = 123 AND unread);

ライターはリーダーをブロックせず、リーダーはライターをブロックしないため、最後の行がすべて正常に更新されるまでTRUEを返します。 LoopUPDATEを取得するまで、上記のFALSEブロックの後にこれが(適切な遅延を伴って)続きます。 そして、完了です。

ORDER BYが大幅なコストを追加する大きなセットの場合は安くなる可能性があります。 OTOH、一致するインデックスがある場合は、ORDER BYを追加しても意味があります...

3.一度に1つ

上記と同様ですが、一度に更新される行は1つだけです。通常はより高価ですが、正しく行われれば、デッドロックの可能性は排除されます。単一の行の処理にすでに長い時間がかかる場合は、これを考慮してください。

詳細な説明(主に上記にも当てはまります)と手順:

2