デッドロックとは
2つ以上のトランザクションが互いの保持するロックの解放を待ち合い、どちらも先に進めなくなった状態である。PostgreSQLはdeadlock_timeout(デフォルト1秒)を超えてロックを待ったときに待ち合わせの循環を検査し、循環を見つけるとトランザクションのひとつを中断する。
中断された側のクライアントには以下のエラーが返る。
ERROR: deadlock detected
DETAIL: Process 88 waits for ShareLock on transaction 741; blocked by process 87.
Process 87 waits for ShareLock on transaction 742; blocked by process 88.
HINT: See server log for query details.
CONTEXT: while updating tuple (0,1) in relation "accounts"
クライアント側のエラーには、どのプロセスがどのロックを待っていたかまでしか出ない。原因のクエリを知るにはサーバログを見る。
サーバログを読む
同じデッドロックがサーバログには以下のように記録される。
2026-07-26 15:16:38.796 UTC [88] ERROR: deadlock detected
2026-07-26 15:16:38.796 UTC [88] DETAIL: Process 88 waits for ShareLock on transaction 741; blocked by process 87.
Process 87 waits for ShareLock on transaction 742; blocked by process 88.
Process 88: UPDATE accounts SET balance = balance + 200 WHERE id = 1;
Process 87: UPDATE accounts SET balance = balance + 100 WHERE id = 2;
2026-07-26 15:16:38.796 UTC [88] HINT: See server log for query details.
2026-07-26 15:16:38.796 UTC [88] CONTEXT: while updating tuple (0,1) in relation "accounts"
2026-07-26 15:16:38.796 UTC [88] STATEMENT: UPDATE accounts SET balance = balance + 200 WHERE id = 1;
クライアント側との違いは、DETAILの後半にProcess 88:から始まるクエリの一覧が加わる点である。デッドロックの調査はクエリの一覧から始める。
待ち合わせの循環
DETAILの前半は、プロセスの待ち合わせの関係を1行ずつ並べている。
Process 88 waits for ShareLock on transaction 741; blocked by process 87.
Process 87 waits for ShareLock on transaction 742; blocked by process 88.
1行目でプロセス88がプロセス87を待ち、2行目でプロセス87がプロセス88を待っている。最後の行のblocked byが1行目のProcessに戻ってくれば循環が閉じている。
ShareLock on transaction 741は、トランザクション741の終了を待っている状態である。ある行にすでに未完了のトランザクションのロックがかかっていると、PostgreSQLは相手のトランザクションIDに対するShareLockを取って終了を待つ。トランザクションIDが出ていれば行レベルのロックの競合であり、UPDATEやDELETEのほか、SELECT FOR UPDATE、一意制約に抵触するINSERT、外部キーの検査でも発生する。
各プロセスのクエリ
DETAILの後半には、循環に加わった各プロセスが最後に実行していたクエリが並ぶ。
Process 88: UPDATE accounts SET balance = balance + 200 WHERE id = 1;
Process 87: UPDATE accounts SET balance = balance + 100 WHERE id = 2;
プロセス88はid = 1を、プロセス87はid = 2を更新しようとして止まっている。ここで待っているのは相手が先に取ったロックなので、それぞれが直前に更新した行は逆になる。
- プロセス87は
id = 1をロック済みで、id = 2を待っている - プロセス88は
id = 2をロック済みで、id = 1を待っている
つまり2つの処理がaccountsの行を逆の順序でロックしており、ロック取得の順序が揃っていない点が原因である。
ログに出るのはデッドロックを起こしたクエリだけであり、先にロックを取得したクエリは出ない。ロック済みの行は、待っているクエリから逆算する。逆算が難しい場合はlog_min_duration_statementでトランザクション内の全クエリを記録しておくと、同じプロセスIDのログをたどって経緯を再現できる。
参考: 【PostgreSQL】log_min_duration_statementでスロークエリをログに出力する
中断されるのは検出したプロセス
エラーを受け取ったのはプロセス88であり、DETAILの1行目に出ているプロセスと一致する。PostgreSQLは循環を検出したプロセス自身のトランザクションを中断する。更新量の少ないトランザクションを選ぶといった調停はしない。
検出するのは、循環が成立したあとに最初のデッドロック検査を迎えたプロセスである。循環を見つけられなかった検査ではタイマーを再設定しない。そのため、先に待ち始めたプロセスは必ずしも検出側にならない。実際には、最後に循環へ加わったプロセスが中断されやすい。
公式ドキュメントも、どのトランザクションが中断されるかは予測が難しく当てにすべきでないとしている。特定のセッションが必ず生き残る前提の実装は避ける。
CONTEXTとSTATEMENT
CONTEXT: while updating tuple (0,1) in relation "accounts"
STATEMENT: UPDATE accounts SET balance = balance + 200 WHERE id = 1;
CONTEXTは待たされた行の物理的な位置(ctid)である。(0,1)はブロック0の1番目のタプルを指す。STATEMENTはエラーになったクエリで、DETAILの該当プロセスの行と同じ内容である。
ctidはテーブル内の物理位置であり、行を更新すると変化する。ログを見た時点でWHERE ctid = '(0,1)'を実行しても、目的の行は取得できない場合がほとんどである。行の特定にはSTATEMENTのWHERE句を使う。
3プロセス以上の場合
デッドロックは2プロセスとは限らない。3つのトランザクションが数珠つなぎに待ち合うと、DETAILの行も3行になる。
ERROR: deadlock detected
DETAIL: Process 130 waits for ShareLock on transaction 745; blocked by process 129.
Process 129 waits for ShareLock on transaction 746; blocked by process 128.
Process 128 waits for ShareLock on transaction 747; blocked by process 130.
Process 130: UPDATE accounts SET balance = balance + 1 WHERE id = 1;
Process 129: UPDATE accounts SET balance = balance + 1 WHERE id = 2;
Process 128: UPDATE accounts SET balance = balance + 1 WHERE id = 3;
130 → 129 → 128 → 130と待ち合いが一周している。プロセスの数が増えても読み方は同じで、最後の行のblocked byが1行目のProcessに戻る。
テーブル単位のロックによるデッドロック
ShareLock on transactionではなく、テーブル自体のロックが出る場合もある。LOCK TABLEやALTER TABLEなど、テーブル全体のロックを取る処理どうしの競合である。
ERROR: deadlock detected
DETAIL: Process 145 waits for AccessExclusiveLock on relation 16385 of database 16384; blocked by process 144.
Process 144 waits for AccessExclusiveLock on relation 16392 of database 16384; blocked by process 145.
Process 145: LOCK TABLE accounts IN ACCESS EXCLUSIVE MODE;
Process 144: LOCK TABLE orders IN ACCESS EXCLUSIVE MODE;
relation 16385はテーブル名ではなくOIDで出力される。名前に変換するにはregclassにキャストする。
SELECT 16385::regclass AS relname,
(SELECT datname FROM pg_database WHERE oid = 16384) AS datname;
relname | datname
----------+---------
accounts | test
(1 row)
regclassへの変換は接続中のデータベースのカタログを参照する。databaseのOIDが接続先と違う場合は、対象のデータベースに接続し直してから実行する。
参考: 【PostgreSQL】テーブルとデータベースのoidを取得する方法
デッドロックに至る前のロック待ちを見る
log_lock_waitsを有効にすると、deadlock_timeoutを超えたロック待ちがデッドロックに至らなかった場合もログに残る。デフォルトはoffである。
ALTER SYSTEM SET log_lock_waits = on;
SELECT pg_reload_conf();
2026-07-26 15:16:54.369 UTC [103] LOG: process 103 still waiting for ShareLock on transaction 743 after 1002.351 ms
2026-07-26 15:16:54.369 UTC [103] DETAIL: Process holding the lock: 96. Wait queue: 103.
2026-07-26 15:16:54.369 UTC [103] CONTEXT: while updating tuple (0,3) in relation "accounts"
2026-07-26 15:16:54.369 UTC [103] STATEMENT: UPDATE accounts SET balance = balance + 100 WHERE id = 3;
2026-07-26 15:16:56.348 UTC [103] LOG: process 103 acquired ShareLock on transaction 743 after 2982.121 ms
still waitingは待ち始めた時点ではなく、deadlock_timeoutを超えた時点で出力される。acquiredで待ちが解消し、after 2982.121 msから約3秒待たされたと分かる。Wait queueはロックの解放を待っているプロセスの一覧で、待ち順に並ぶ。自分自身も含まれるため、103が自分だけの場合は待ち行列に他のプロセスがいない状態である。
デッドロックは長いロック待ちの延長線上で起きる。デッドロックが顕在化する前の段階で、ロック待ちの多い処理をlog_lock_waitsで洗い出しておく。
デッドロックの検出もログに出る
log_lock_waitsが有効な場合、デッドロックのエラーの直前に検出のログが出る。
2026-07-26 15:16:38.796 UTC [88] LOG: process 88 detected deadlock while waiting for ShareLock on transaction 741 after 1001.103 ms
2026-07-26 15:16:38.796 UTC [88] DETAIL: Process holding the lock: 87. Wait queue: .
detected deadlockの行を見れば、どのプロセスが検出側かがすぐに分かる。log_lock_waitsがoffの場合はERROR: deadlock detectedしか出ないため、デッドロックを調査するなら有効にしておく。
セッション単位で試す
log_lock_waitsとdeadlock_timeoutはスーパーユーザーであればセッション単位で変更できる。サーバ全体の設定を変えずに、調査対象の処理だけログを出せる。
SET log_lock_waits = on;
SET deadlock_timeout = '200ms';
2026-07-26 15:30:49.730 UTC [102] LOG: process 102 still waiting for ShareLock on transaction 743 after 205.477 ms
閾値を下げたセッションからのみ、205msの待ちがログに出ている。
発生回数を数える
個々のログではなく全体の傾向を見るには、pg_stat_databaseのdeadlocksを確認する。
SELECT datname, deadlocks, stats_reset FROM pg_stat_database WHERE datname = current_database();
datname | deadlocks | stats_reset
---------+-----------+-------------
test | 3 |
deadlocksは統計のリセット以降の累計である。定期的に取得して差分を見ると、発生頻度の増減を追える。track_countsがonの場合のみ計上され、pg_stat_reset()でリセットされる。
ログにアプリケーション名を含める
デッドロックのログにはプロセスIDしか出ないため、複数のアプリケーションが同じデータベースを使っていると発生元を絞り込めない。log_line_prefixにユーザー名・データベース名・アプリケーション名を追加する。
ALTER SYSTEM SET log_line_prefix = '%m [%p] %q%u@%d %a ';
SELECT pg_reload_conf();
| 記法 | 内容 |
|---|---|
%m | ミリ秒付きのタイムスタンプ |
%p | プロセスID |
%q | 自身は何も出力せず、後続をセッションのプロセスに限定する |
%u | ユーザー名 |
%d | データベース名 |
%a | アプリケーション名 |
接続時にapplication_nameを設定しておくと、ログにそのまま出る。
SET application_name = 'worker-a';
2026-07-26 15:17:52.396 UTC [180] postgres@test worker-a ERROR: deadlock detected
2026-07-26 15:17:52.396 UTC [180] postgres@test worker-a DETAIL: Process 180 waits for ShareLock on transaction 751; blocked by process 181.
Process 181 waits for ShareLock on transaction 750; blocked by process 180.
Process 180: UPDATE accounts SET balance = balance + 100 WHERE id = 2;
Process 181: UPDATE accounts SET balance = balance + 200 WHERE id = 1;
worker-aが中断された側と分かる。バッチ処理やジョブワーカーごとにapplication_nameを割り当てておくと、ログだけで発生元を特定できる。
参考: 【PostgreSQL】postgresql.confの場所を探す(show config_file)
検出までの時間を変える
deadlock_timeoutはデッドロックの検査を始めるまでの待ち時間である。短くすると検出は速くなるが、デッドロックでない通常のロック待ちに対しても検査が走るため、待ち合わせの多いシステムでは負荷が増える。負荷の高いサーバではむしろ1秒より長くしてよい。
SHOW deadlock_timeout;
deadlock_timeout
------------------
1s
log_lock_waitsのログが出る閾値もdeadlock_timeoutである。片方だけを目的に変更すると、もう片方の挙動も変わる点に注意する。ロック待ちを細かく調べたい場合は、前述のセッション単位の設定で一時的に短くする。
ログから対策へ
ログで判明するのは、循環に加わったクエリとロックの対象である。そこから取れる対策は以下の3つに集約される。
ロックの取得順序を揃える
複数の行やテーブルをロックする場合、ロックの取得順序をすべての処理で統一する。行の更新であれば主キーの昇順に揃えると、循環が生じなくなる。
SELECT * FROM accounts WHERE id IN (1, 2) ORDER BY id FOR UPDATE;
参考: 【PostgreSQL】SELECT FOR UPDATEで行をロックする
最初から必要な強さのロックを取る
SELECTで読んでから同じ行をUPDATEする処理は、途中でロックを強い側へ昇格させる。複数のセッションが同時に昇格しようとすると待ち合いが生じる。あとで更新する予定があるなら、最初のSELECTの時点でFOR UPDATEを付けて必要な強さのロックを取る。
トランザクションを短くする
ロックの保持時間が短いほど、待ち合わせの循環が成立する余地は小さくなる。トランザクションの内側から外部APIの呼び出しなど時間のかかる処理を追い出す。
リトライする
順序を揃えても、複数のインデックスや外部キー制約が絡むと完全には防ぎきれない。デッドロックによる中断はトランザクション全体がロールバックされているため、そのまま再実行できる。SQLSTATE 40P01を捕捉して数回リトライする実装を入れておく。
