本番環境で突然 ORA-00060: deadlock detected while waiting for resource が発生すると、焦って対応しがちです。しかしOracleのデッドロックは自動で検出・解消される仕組みがあるため、冷静に原因を特定し再発防止策を打つことが重要です。本記事ではデッドロックの発生メカニズムから、トレースファイルの読み方、ロック調査SQL、予防策まで体系的に解説します。
- デッドロックの発生メカニズム(なぜ2つのセッションが互いに待ち合うのか)
- ORA-00060 発生時の Oracle の自動検出・ロールバック動作
- トレースファイル(.trc)の場所と読み方
- V$LOCK / V$SESSION / DBA_BLOCKERS を使ったロック調査SQL
- 行ロックデッドロックと ITL デッドロックの違い
- デッドロックを予防する設計パターン
- SELECT FOR UPDATE と NOWAIT / WAIT / SKIP LOCKED の使い分け
デッドロックの発生メカニズム
デッドロックは、2つ以上のセッションが互いに相手が保持しているロックを待つ状態です。どちらも進めず、どちらも引かないため、永遠に待ち続けることになります。
最も典型的な発生パターンを図示します。
セッションA セッションB
───────── ─────────
UPDATE orders SET ... WHERE order_id = 1; (orders の行1 をロック)
UPDATE orders SET ... WHERE order_id = 2; (orders の行2 をロック)
UPDATE orders SET ... WHERE order_id = 2; → セッションBのロック解放を待機...
UPDATE orders SET ... WHERE order_id = 1; → セッションAのロック解放を待機...
→ 両者が互いのロック解放を待つ → デッドロック発生
ORA-00060 発生時の Oracle の動作
Oracleにはデッドロック検出機構が内蔵されており、デッドロックを自動的に検知します。検出後の動作は次のとおりです。
- デッドロックを検出する(通常3秒以内)
- 一方のセッションの最後に実行したSQL文だけをロールバックする(トランザクション全体ではない)
- ロールバックされたセッションに
ORA-00060を返す - もう一方のセッションはロック解放を受けて処理を続行する
- トレースファイル(
.trc)にデッドロックグラフを出力する
トレースファイルの場所と読み方
デッドロック発生時、Oracleはバックグラウンドで自動的にトレースファイルを生成します。このファイルにはデッドロックグラフが含まれており、原因の特定に不可欠です。
トレースファイルの場所を確認する
-- ADR(Automatic Diagnostic Repository)のパスを確認 SELECT value FROM v$diag_info WHERE name = 'Diag Trace'; -- 例: /u01/app/oracle/diag/rdbms/orcl/orcl/trace/ -- 最近のトレースファイルをOSコマンドで検索 -- ls -lt /u01/app/oracle/diag/rdbms/orcl/orcl/trace/*.trc | head
デッドロックグラフの読み方
トレースファイルを開くと次のような「Deadlock Graph」セクションがあります。
DEADLOCK DETECTED ( ORA-00060 )
[Transaction Deadlock]
The following deadlock is not an ORACLE error. It is a
temporary situation that will automatically resolve itself.
Deadlock graph:
---------Blocker(s)-------- ---------Waiter(s)---------
Resource Name process session holds waits process session holds waits
TX-00070012-0000042f 22 145 X 25 203 X
TX-000a0008-00000531 25 203 X 22 145 X
session 145: DID 0001-0016-00000003
session 203: DID 0001-0019-00000005
Rows waited on:
Session 145: obj - rowid = 000124AB - AAABSrAAFAAAACjAAA
(dictionary objn - 74923, file - 5, block - 169, slot - 0)
Session 203: obj - rowid = 000124AB - AAABSrAAFAAAACjAAB
(dictionary objn - 74923, file - 5, block - 169, slot - 1)
| 項目 | 意味 |
|---|---|
| Resource Name (TX-…) | トランザクションロックのリソースID |
| session | セッションID(V$SESSIONのSID) |
| holds X | 排他ロック(X)を保持している |
| waits X | 排他ロック(X)を待機している |
| Rows waited on | 待機している行のROWIDとオブジェクトID |
-- オブジェクトIDからテーブル名を特定する SELECT owner, object_name, object_type FROM dba_objects WHERE object_id = 74923; -- 例: HR.ORDERS -- ROWIDからレコードの内容を確認する SELECT * FROM hr.orders WHERE ROWID = 'AAABSrAAFAAAACjAAA';
ロック調査SQL
デッドロック発生中、またはロック待ちが発生している状況でリアルタイムに調査するためのSQLです。
SELECT
w.sid AS waiting_sid,
w.serial# AS waiting_serial,
w.username AS waiting_user,
w.sql_id AS waiting_sql_id,
w.event AS wait_event,
b.sid AS blocking_sid,
b.serial# AS blocking_serial,
b.username AS blocking_user,
b.sql_id AS blocking_sql_id
FROM v$session w
JOIN v$session b ON w.blocking_session = b.sid
WHERE w.blocking_session IS NOT NULL
ORDER BY w.sid;
SELECT
s.sid,
s.serial#,
s.username,
l.type, -- TX=トランザクションロック, TM=テーブルロック
l.id1,
l.id2,
DECODE(l.lmode, 0, 'None', 1, 'Null', 2, 'Row-S', 3, 'Row-X',
4, 'Share', 5, 'S/Row-X', 6, 'Exclusive') AS lock_mode,
DECODE(l.request, 0, 'None', 1, 'Null', 2, 'Row-S', 3, 'Row-X',
4, 'Share', 5, 'S/Row-X', 6, 'Exclusive') AS request_mode,
l.block -- 1=他セッションをブロック中, 0=ブロックしていない
FROM v$lock l
JOIN v$session s ON l.sid = s.sid
WHERE l.type IN ('TX', 'TM')
ORDER BY l.type, s.sid;
SELECT
s.sid,
s.username,
o.object_name,
o.object_type,
DECODE(l.locked_mode, 2, 'Row-S', 3, 'Row-X', 4, 'Share',
5, 'S/Row-X', 6, 'Exclusive') AS locked_mode
FROM v$locked_object l
JOIN dba_objects o ON l.object_id = o.object_id
JOIN v$session s ON l.session_id = s.sid
ORDER BY o.object_name, s.sid;
セッション確認・強制切断の詳しい方法は「【Oracle】セッションの確認・強制切断方法完全ガイド」を参照してください。
デッドロックの種類
行ロックデッドロック(TX-TX)
最も一般的なデッドロックです。2つのセッションが異なる行を異なる順序で更新しようとして発生します。上述の発生メカニズムの例がこれに該当します。
自己参照デッドロック
外部キー制約のあるテーブルで、親テーブルと子テーブルを異なる順序で更新した場合に発生することがあります。OracleはFKの参照先(親テーブル)のロックも暗黙的に取得するため、明示的なUPDATEでは見えにくいデッドロックが起きます。
ITLデッドロック
ITL(Interested Transaction List)はブロックヘッダ内のトランザクション管理領域です。同一ブロック内で多数のトランザクションが同時にDMLを実行すると、ITLスロットが不足して待ちが発生し、まれにデッドロックに至ります。INITRANS の値を増やすことで回避できます。
-- テーブルの INITRANS を増やす(デフォルトは 1 〜 2) ALTER TABLE orders INITRANS 10; -- インデックスの INITRANS を増やす ALTER INDEX idx_orders_date INITRANS 10;
ビットマップインデックスデッドロック
ビットマップインデックスはインデックスエントリが行範囲単位でロックされるため、通常のB*Treeインデックスよりもデッドロックが発生しやすくなります。OLTP(頻繁な更新がある)テーブルではビットマップインデックスの使用は推奨されません。
デッドロックを予防する設計パターン
テーブル・行の更新順序をルール化する
デッドロックの最も効果的な予防策は、全てのトランザクションでテーブルと行の更新順序を統一することです。
-- ルール: orders → order_items の順で更新する(全プログラムで統一) -- BAD: プログラムAは orders→items、プログラムBは items→orders の順序(デッドロックの原因) -- GOOD: 全プログラムで orders→items の順序に統一 -- 同一テーブル内でも主キー昇順で更新する UPDATE orders SET status = 'shipped' WHERE order_id = 1; UPDATE orders SET status = 'shipped' WHERE order_id = 2; -- 全セッションが order_id 昇順で更新すればデッドロックは起きない
トランザクションを短く保つ
ロック保持時間が長いほどデッドロックの発生確率は上がります。トランザクション内で不要な処理(ファイルI/O・外部API呼び出し等)を挟まないようにしましょう。
SELECT FOR UPDATE で明示ロックする
更新対象の行を事前にロックしてから処理することで、ロック取得順序を明確にできます。
-- 更新対象を事前にロックしてから UPDATE する SELECT * FROM orders WHERE order_id IN (1, 2) ORDER BY order_id -- ロック順序を主キー昇順に統一 FOR UPDATE NOWAIT; -- ロックが取れなければ即座にエラー -- ロック取得後に安全に UPDATE UPDATE orders SET status = 'shipped' WHERE order_id = 1; UPDATE orders SET status = 'shipped' WHERE order_id = 2; COMMIT;
NOWAIT / WAIT / SKIP LOCKED の使い分け
| オプション | 動作 | 用途 |
|---|---|---|
FOR UPDATE NOWAIT |
ロックが取れなければ即座に ORA-00054 を返す | 対話的アプリケーション(ユーザーに即通知) |
FOR UPDATE WAIT 5 |
最大5秒間ロック取得を待つ。超えたら ORA-30006 | 短時間なら待ってよい場合 |
FOR UPDATE SKIP LOCKED |
ロック中の行をスキップして残りの行だけロックする | キュー処理・並列ワーカー(ロック中の行は他ワーカーに任せる) |
アプリケーション側のORA-00060対処パターン
DECLARE
deadlock_detected EXCEPTION;
PRAGMA EXCEPTION_INIT(deadlock_detected, -60); -- ORA-00060
v_max_retry CONSTANT PLS_INTEGER := 3;
BEGIN
FOR i IN 1..v_max_retry LOOP
BEGIN
-- デッドロックが起きやすい処理
UPDATE orders SET status = 'processing' WHERE order_id = :id;
COMMIT;
EXIT; -- 成功したらループを抜ける
EXCEPTION
WHEN deadlock_detected THEN
ROLLBACK;
IF i = v_max_retry THEN
RAISE; -- 最終リトライでも失敗したら呼び出し元にエラーを返す
END IF;
DBMS_SESSION.SLEEP(i); -- 待機時間を段階的に増やす(1秒, 2秒, 3秒)
END;
END LOOP;
END;
/
ORA-00060は最後のSQL文だけロールバックされますが、リトライ時はトランザクション全体を
ROLLBACK してからやり直すのが安全です。部分的に成功した状態から再開すると、データの整合性が保証されません。
まとめ
ORA-00060(デッドロック)はOracleが自動検出して一方のセッションの最後のSQL文をロールバックすることで解消されます。致命的なエラーではありませんが、再発防止には根本原因の特定と設計の見直しが不可欠です。
- デッドロックは2つ以上のセッションが互いのロック解放を待つ状態で発生する
- Oracleは自動検出し、一方のセッションの最後のSQL文のみをロールバックする(トランザクション全体ではない)
- トレースファイル(
.trc)の「Deadlock Graph」セクションで、どのセッションがどのリソースを待っていたかを特定できる V$LOCK/V$SESSION/V$LOCKED_OBJECTでリアルタイムのロック状況を調査する- 最も効果的な予防策はテーブル・行の更新順序を全プログラムで統一すること
SELECT FOR UPDATE+NOWAIT/WAIT/SKIP LOCKEDで明示的にロックを取得し、競合を制御する- ITLデッドロックは
INITRANSの値を増やすことで対策する - アプリケーション側ではORA-00060を検知してリトライする実装が必要

