38

PostgreSQLのデッドロックについて読んで少し混乱しています。

典型的なデッドロックの例は次のとおりです。

-- Transaction 1
UPDATE customer SET ... WHERE id = 1
UPDATE customer SET ... WHERE id = 2

-- Transaction 2
UPDATE customer SET ... WHERE id = 2
UPDATE customer SET ... WHERE id = 1

しかし、次のようにコードを変更するとどうなりますか?

-- Transaction 1
UPDATE customer SET ... WHERE id IN (1, 2)

-- Transaction 2
UPDATE customer SET ... WHERE id IN (1, 2)

ここでデッドロックが発生する可能性はありますか?

基本的に私の質問は次のとおりです。2番目のケースでは、PostgreSQLは行を1つずつロックしますか、それともWHERE条件の対象となるスコープ全体をロックしますか?

前もって感謝します!

4

2 に答える 2

46

PostgreSQLでは、行は更新されるとロックされます。実際、これが実際に機能する方法は、各タプル(行のバージョン)に、xminそのタプルを(挿入または更新によって)現在のタプルにしたトランザクションを示すために呼び出されるシステムフィールドがあることです。そして、xmaxどのトランザクションがそのタプルを(更新または削除によって)期限切れにしたかを示すために呼び出されるシステムフィールド。データにアクセスすると、アクティブな「スナップショット」をこれらの値と照合することにより、各タプルをチェックして、トランザクションに表示されているかどうかを判断します。

UPDATEを実行していて、検索条件に一致するタプルに、スナップショットに表示されるxminとアクティブなトランザクションのxmaxがある場合、そのトランザクションが完了するのを待ってブロックします。タプルを最初に更新したトランザクションがロールバックすると、トランザクションがウェイクアップして行を処理します。最初のトランザクションがコミットされると、トランザクションはウェイクアップし、現在のトランザクション分離レベルに応じてアクションを実行します。

明らかに、デッドロックは、これが異なる順序で行に発生した結果です。RAMには、すべての行に対して同時に取得できる行レベルのロックはありませんが、行が同じ順序で更新される場合、循環ロックを使用することはできません。残念ながら、提案されたIN(1, 2)構文はそれを保証するものではありません。セッションが異なれば、異なるコスト要因がアクティブになる可能性があります。バックグラウンドの「分析」タスクによって、あるプランの生成と別のプランの生成の間でテーブルの統計が変更される可能性があります。または、seqscanを使用していて、新しいseqscanを引き起こすPostgreSQL最適化の影響を受ける可能性があります。すでに進行中の1つに参加し、「ループアラウンド」してディスクI/Oを削減します。

アプリケーションコードまたはカーソルを使用して、同じ順序で一度に1つずつ更新を行うと、デッドロックではなく、単純なブロックのみが発生します。ただし、一般に、リレーショナルデータベースはシリアル化に失敗する傾向があるため、SQLSTATEに基づいてリレーショナルデータベースを認識し、トランザクション全体を最初から自動的に再試行するフレームワークを介してアクセスするのが最適です。PostgreSQLでは、シリアル化の失敗は常に40001または40P01のSQLSTATEになります。

http://www.postgresql.org/docs/current/interactive/mvcc-intro.html

于 2012-04-20T12:14:34.593 に答える
1

PostgreSQLは行を1つずつロックしますか、それともスコープ全体をロックしますか

PostgreSQLは行を1つずつロックします。

そして苛立たしいことに、選択と挿入の場合のように更新(または削除)を注文する方法はありません。

解決策は、レコードを事前にロックしSELECT FOR UPDATEて自己結合することです。

UPDATE customer AS c SET ...
FROM (
  SELECT ctid
  FROM customer
  WHERE id IN (1, 2)
  ORDER BY id -- the optimal ordering varies, but it must be strict and consistent
  FOR UPDATE
) AS c2
WHERE c.ctid = c2.ctid

(ここでは、行の物理IDを使用しますctid。これにより、結合が少し速くなります。)

PostgreSQLはレコードを検索し、レコードを順番にロックしてから、レコードを更新します。

クエリプランを調べて、これを納得させることができます。

UPDATE多少のオーバーヘッドはありますが、特にそれが最初の場所での軽い操作ではないことを考えると、それは最小限です。

于 2022-02-17T18:37:30.623 に答える