デッドロック:検出と回避
デッドロックが発生する仕組みとPostgresがそれを検出する方法を理解し、防止するロック取得順序のルールを設計します。
「デッドロック:検出と回避」はCoddyKit上の無料SQL Academyレッスンです。 これはレッスン3/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはSQL Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 SQL Academyコースには全4レッスンが含まれています。
デッドロックとは
2つのトランザクションがそれぞれ相手の必要とするロックを保持しているため、どちらも先に進めない状態です。データベースはこの循環を検出し、一方のトランザクションを中止します。
典型的なデッドロック
Tx Aが行1をロックし、Tx Bが行2をロックします。Aが行2を要求し、Bが行1を要求すると、処理が停止します。
-- Tx A:
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
-- waiting for B...
-- Tx B:
BEGIN;
UPDATE accounts SET balance = balance - 50 WHERE id = 2;
UPDATE accounts SET balance = balance + 50 WHERE id = 1;
-- waiting for A...
-- ERROR: deadlock detectedPostgreSQLによるデッドロック検出
deadlock_timeout(デフォルトは1秒)ごとに、PostgreSQLはロックの循環を検査します。検出すると、エラーコード 40P01 で一方のトランザクションを中止します。
ERROR: deadlock detected
DETAIL: Process 1234 waits for ShareLock on transaction 5678 ...ロック取得順序のルール
解決策は、すべてのコードパスで常に同じ順序でロックを取得することです。
-- Always update the lower id first:
UPDATE accounts SET balance = balance - 100 WHERE id = LEAST(:from, :to);
UPDATE accounts SET balance = balance + 100 WHERE id = GREATEST(:from, :to);競合の多い行でのデッドロック
同じ競合の多い行を頻繁に更新すると、デッドロックではなくロック待ちが発生することがよくあります。キューイングを使う、競合の多い行をパーティション分割する、またはアプリケーションコードで更新を直列化してください。
FOR UPDATEで読み取り時に行をロック
後で予期しない事態が起きないよう、読み取り時に書き込みロックを取得します。
BEGIN;
SELECT * FROM accounts WHERE id IN (1, 2) ORDER BY id FOR UPDATE;
-- both rows locked in id order
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;ロックされた行をスキップ
キューテーブルで「利用可能な行を1つ取得する」パターンです。
SELECT * FROM jobs
WHERE status = 'pending'
ORDER BY created_at
LIMIT 1
FOR UPDATE SKIP LOCKED;
-- skips rows other workers have lockedNOWAIT
待機せず、すぐに失敗させます。
SELECT * FROM accounts WHERE id = 1 FOR UPDATE NOWAIT;
-- ERROR: could not obtain lock on row in relation "accounts"デッドロックの診断
log_lock_waits を有効にし、デッドロックのコンテキストをログに記録します。ログエントリには、両方のトランザクションとそのクエリが表示されます。
アプリケーションのリトライループ
デッドロックからは復旧できます。中止されたトランザクションをリトライしてください。
for (let attempt = 0; attempt < 3; attempt++) {
try {
await runTransaction();
break;
} catch (e) {
if (e.code === '40P01') continue; // deadlock
throw e;
}
}ロック範囲を小さくする
トランザクションを短くしてください。触れたすべての行は COMMIT までロックされたままになります。トランザクション内でHTTP呼び出しや長時間の計算を行わないでください。
ロックのエスカレーションを避けるため外部キーにインデックスを付ける
親を削除すると、すべての子行が検査されます。外部キーにインデックスがないと、テーブル全体のスキャンに加えて行ロックも発生します。すべての外部キー列にインデックスを付けてください。
まとめ
デッドロックは発生するものです。発生しにくいように設計してください。
- 一貫した順序でロックを取得する
- FOR UPDATE を早い段階で使い、意図を明示する
- キューには SKIP LOCKED を使う
- デッドロックエラー(40P01)でリトライする
- トランザクションを短く保つ
クイックチェック
デッドロックを防ぐために最も信頼できる設計原則は何ですか。
よくある質問
「デッドロック:検出と回避」レッスンは無料ですか?
はい。「デッドロック:検出と回避」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、SQL Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 SQL Academyコースには全4レッスンが含まれています。
「デッドロック:検出と回避」で何を学びますか?
デッドロックが発生する仕組みとPostgresがそれを検出する方法を理解し、防止するロック取得順序のルールを設計します。 ブラウザで直接実行するハンズオンコードでSQL Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
SQL Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのSQL Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン3/4です。
「デッドロック:検出と回避」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このSQL Academyレッスンでコードを書いて実行できますか?
はい。すべてのSQL Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。