次の状況で、Postgresql 9.2 サーバーでまれにデッドロックが発生することがわかりました。
T1 がバッチ操作を開始します。
UPDATE BB bb SET status = 'PROCESSING', chunk_id = 0 WHERE bb.status ='PENDING'
AND bb.bulk_id = 1 AND bb.user_id IN (SELECT user_id FROM BB WHERE bulk_id = 1
AND chunk_id IS NULL AND status ='PENDING' LIMIT 2000)
T1 が数百ミリ秒程度 (BB には数百万行あります) 後にコミットすると、複数のスレッドが新しいトランザクション (スレッドごとに 1 つのトランザクション) を開始します。クエリ:
選択する場合:
SELECT *, RANK() as rno OVER(ORDER BY user_id) FROM BB WHERE status = 'PROCESSING' AND bulk_id = 1 and rno = $1
そして更新:
UPDATE BB set datetime=$1, status='DONE', message_id=$2 WHERE bulk_id=1 AND user_id=$3
(user_id、bulk_id には UNIQUE 制約があります)。
外部の状況の問題により、別のトランザクション T2 は、T1 がコミットされたほぼ直後に T1 で同じクエリを実行します (アイテムが「PROCESSING」としてマークされる最初のバッチ操作)。
UPDATE BB bb SET status = 'PROCESSING', chunk_id = 0 WHERE bb.status ='PENDING'
AND bb.bulk_id = 1 AND bb.user_id IN (SELECT user_id FROM BB WHERE bulk_id = 1
AND chunk_id IS NULL AND status ='PENDING' LIMIT 2000)
ただし、これらのアイテムは「PROCESSING」としてマークされていますが、このクエリは、ワーカースレッドからの一部の更新 (前述のようにバッチで行われます) でデッドロックします。私の理解では、これは、使用する READ_COMMITTED 分離レベル (デフォルト) では発生しないはずです。ワーカー スレッドはコミット後に実行されるため、T1 がコミットしたことは確かです。
編集:私が片付けなければならないことの1つは、T2がT1の後、コミットする前に開始することです。ただし、同じ行で取得した write_exclusive タプル ロックSELECT for UPDATE
(上記のクエリのいずれにも影響されない) のため、バッチ更新クエリを実行する前に T1 がコミットするのを待ちます。