ORA-00060: deadlock detected エラーの原因と対処法|Oracleデッドロックの仕組み
- 作成日 2026.06.16
- 更新日 2026.06.17
- Oracle Database
複数ユーザーが同時操作する業務システムで、以下のエラーで悩まされた経験はありませんか?
ORA-00060: deadlock detected while waiting for resource
ORA-00060: リソース待機中にデッドロックが検出されました
デッドロックは、複数のセッションが互いに相手のロック解放を待ち続け、永久に進めなくなる状態です。Oracleは自動検出して片方のセッションをロールバックしますが:
- 同じトランザクションが繰り返し失敗する
- バッチ処理が時々失敗する
- 競合の組み合わせが特定できない
- トレースファイルを見ても何が起きているか分からない
- 外部キーのあるテーブル更新でデッドロックが頻発する
- インデックスを増やしたらデッドロックが減った/増えた
このように、ORA-00060はシステム設計の問題を反映していることが多く、表面的な対処では再発を防げません。
本記事では、ORA-00060エラーのすべての原因と対処法を、現場で即使えるトラブルシューティング手順として整理します。デッドロックの基礎メカニズムから、トレースファイルの読み方、外部キー未インデックス問題、ITLデッドロック、アプリ実装パターン、リトライロジック、予防ベストプラクティスまで完全網羅。この1本でORA-00060との戦いを終わらせましょう。
- 1. 結論:今すぐやるべき3ステップ
- 2. まず押さえる:デッドロックの基礎メカニズム
- 3. ORA-00060の原因カテゴリ
- 4. 【原因①】外部キー列の未インデックス問題(最頻出・要注意)
- 5. 【原因②】アクセス順序の不一致
- 6. 【原因③】ITLデッドロック
- 7. 【原因④】ビットマップインデックスの並行更新
- 8. 【原因⑤】MAX+1方式での連番採番
- 9. トレースファイルの解析
- 10. デッドロック発生時のロック競合確認
- 11. アプリケーション側の対処パターン
- 12. デッドロック予防のベストプラクティス
- 13. 関連エラーと違い
- 14. トラブルシューティング・チェックリスト
- 15. よくある質問(FAQ)
- 15.1. Q1. デッドロックは完全に防げますか?
- 15.2. Q2. ORA-00060が出た時、データはどうなりますか?
- 15.3. Q3. トレースファイルが見つかりません
- 15.4. Q4. 外部キー列にインデックスを付けたら逆に遅くなりました
- 15.5. Q5. テーブル全体ロックが起きるのはなぜ?
- 15.6. Q6. PL/SQLの中でORA-00060が出た時の挙動は?
- 15.7. Q7. デッドロックトレースが大量に出てディスクを圧迫します
- 15.8. Q8. 開発環境では再現せず本番だけで起きます
- 15.9. Q9. SELECT FOR UPDATEはどう使い分けますか?
- 15.10. Q10. RAC環境でデッドロックは増えますか?
- 16. 参考リンク・関連資料
- 17. まとめ
結論:今すぐやるべき3ステップ
時間がない方向けに、まず実施すべき手順を示します。
ステップ1:トレースファイルを確認
# alert.logの場所を確認
sqlplus / as sysdba
SQL> SELECT VALUE FROM V$DIAG_INFO WHERE NAME = 'Diag Trace';
# alert.logでデッドロック発生時刻を特定
tail -100 alert.log | grep -A 2 "deadlock"
ステップ2:トレースファイルから関与SQLを特定
alert.logに記載のトレースファイル名(例: orcl_ora_12345.trc)を開いて、デッドロック関与SQLを確認。
ステップ3:外部キー列にインデックスを追加
ORA-00060の最頻出原因はFK列の未インデックスです:
-- 外部キー列にインデックスが無いものを抽出
SELECT TABLE_NAME, CONSTRAINT_NAME, COLUMN_NAME
FROM USER_CONS_COLUMNS c
WHERE CONSTRAINT_NAME IN (
SELECT CONSTRAINT_NAME FROM USER_CONSTRAINTS WHERE CONSTRAINT_TYPE = 'R'
)
AND NOT EXISTS (
SELECT 1 FROM USER_IND_COLUMNS i
WHERE i.COLUMN_NAME = c.COLUMN_NAME AND i.TABLE_NAME = c.TABLE_NAME
);
該当列にインデックスを作成:
CREATE INDEX idx_child_parent_id ON child_table(parent_id);
それでも解決しない場合は、以下の詳細な原因分析へ進んでください。
まず押さえる:デッドロックの基礎メカニズム
ORA-00060を理解するには、デッドロックがなぜ起きるかを知る必要があります。
デッドロックの典型パターン
セッションA セッションB
───────────────────── ─────────────────────
UPDATE A.row1 UPDATE B.row2
(A.row1にロック取得) (B.row2にロック取得)
UPDATE B.row2 UPDATE A.row1
(B.row2のロック待ち) (A.row1のロック待ち)
← 互いに相手のロック解放を待ち続ける →
↓
Oracleが自動検出 → 片方をORA-00060でロールバック
両セッションが互いに必要なリソースを保持し、相手の解放を待つ循環依存が生じた状態です。
通常のロック待ちとの違い
| 状況 | エラー |
|---|---|
| 単純なロック待ち(解放を待てば進める) | エラーなし(無限待機 or タイムアウト) |
| 循環依存(永久に進めない) | ORA-00060(デッドロック) |
| ロック取得タイムアウト | ORA-30006 / ORA-04068 |
| NOWAIT指定で取得失敗 | ORA-00054 |
Oracleの自動検出
Oracleは内部でロック待機グラフを構築しており、循環を検出すると約3秒以内に自動でデッドロックを判定します。片方のセッションを強制ロールバックして、もう片方は処理続行できる状態にします。
ORA-00060の原因カテゴリ
| カテゴリ | 原因例 | 頻度 |
|---|---|---|
| アクセス順序系 | 複数行を逆順で更新 | 多 |
| 外部キー系 | FK列にインデックスがなく親更新でテーブルロック | 最多 |
| ITLデッドロック | INITRANS不足によるブロック内競合 | 中 |
| ビットマップインデックス系 | 同じビットマップエントリへの並行更新 | 中 |
| オートインクリメント系 | シーケンスではなくMAX+1方式 | 中 |
| ライブラリキャッシュ系 | DDL中の並行アクセス | 少 |
| PK/UK重複系 | 同じキー値の同時INSERT | 少 |
特に「外部キー列のインデックス不足」が頻発の主因で、設計時の見落としとして多い問題です。
【原因①】外部キー列の未インデックス問題(最頻出・要注意)
ORA-00060で最も多い原因ですが、開発者が気付きにくい問題です。
何が起きているか
外部キー(FK)制約のある子テーブルで、親テーブルを更新する際に子テーブル全体に共有ロックがかかります。FK列にインデックスが無いと、Oracleは整合性チェックのため広範囲なロックを取得し、他セッションの更新と競合します。
問題の再現
-- 親テーブル
CREATE TABLE department (dept_id NUMBER PRIMARY KEY, name VARCHAR2(100));
-- 子テーブル(FK列にインデックスなし)
CREATE TABLE employee (
emp_id NUMBER PRIMARY KEY,
dept_id NUMBER REFERENCES department(dept_id), -- ❌ インデックス無し
name VARCHAR2(100)
);
-- セッションAでemployee更新
UPDATE employee SET name = 'A' WHERE emp_id = 1;
-- セッションBで親テーブル更新(または削除)
UPDATE department SET name = 'B' WHERE dept_id = 1; -- ロック待ち発生
-- セッションAで department を更新しようとすると…
UPDATE department SET name = 'C' WHERE dept_id = 2; -- ORA-00060 デッドロック
該当箇所の検出SQL
-- インデックスが無い外部キー列を一覧
SELECT
cc.OWNER,
cc.TABLE_NAME,
cc.CONSTRAINT_NAME,
cc.COLUMN_NAME,
cc.POSITION
FROM DBA_CONS_COLUMNS cc
JOIN DBA_CONSTRAINTS c
ON cc.OWNER = c.OWNER
AND cc.CONSTRAINT_NAME = c.CONSTRAINT_NAME
WHERE c.CONSTRAINT_TYPE = 'R' -- 外部キー制約
AND c.OWNER NOT IN ('SYS', 'SYSTEM')
AND NOT EXISTS (
SELECT 1
FROM DBA_IND_COLUMNS ic
WHERE ic.TABLE_OWNER = cc.OWNER
AND ic.TABLE_NAME = cc.TABLE_NAME
AND ic.COLUMN_NAME = cc.COLUMN_NAME
AND ic.COLUMN_POSITION = cc.POSITION
)
ORDER BY cc.TABLE_NAME, cc.CONSTRAINT_NAME, cc.POSITION;
対処:FK列にインデックス追加
CREATE INDEX idx_employee_dept_id ON employee(dept_id);
これで親テーブル更新時のロック範囲が大幅に狭まり、デッドロック頻度が劇的に減ります。
なぜFK列にインデックスを推奨するか
| メリット | 内容 |
|---|---|
| デッドロック予防 | 親更新時のロック範囲縮小 |
| 外部結合の高速化 | JOIN クエリのパフォーマンス向上 |
| 親レコード削除の高速化 | 子参照チェックがインデックススキャンに |
| カスケード操作の高速化 | ON DELETE CASCADE等 |
外部キー設計時は列にインデックス作成をセットにするのが標準的なベストプラクティスです。
【原因②】アクセス順序の不一致
複数の行を更新する際、セッションごとにアクセス順序が異なるとデッドロックが起きます。
問題の例
-- セッションA:emp_id 順
UPDATE employee SET salary = salary * 1.1 WHERE emp_id IN (1, 2, 3);
-- セッションB:emp_id 逆順
UPDATE employee SET dept_id = 99 WHERE emp_id IN (3, 2, 1);
並行実行されると:
- A は emp_id=1 → 2 → 3 の順にロック
- B は emp_id=3 → 2 → 1 の順にロック
- 中間で逆方向の待機状態 → デッドロック
対処:アクセス順序の統一
すべてのアプリで同じ順序で行を取得・更新する規約を設けます。
-- ORDER BYで順序固定
SELECT * FROM employee WHERE dept_id = 10
ORDER BY emp_id -- 必ずemp_id順
FOR UPDATE;
ループ更新の場合
// 推奨:取得対象を ORDER BY で固定順
String sql = "SELECT emp_id FROM employee WHERE dept_id = ? ORDER BY emp_id";
// 取得した順にUPDATE
複数テーブルに跨る場合も、テーブル更新順序を統一します。例: 必ず「親→子」、「ID順」など。
【原因③】ITLデッドロック
INITRANS(Initial Transactions)パラメータ不足による、同じデータブロック内での競合です。
何が起きているか
Oracleの各データブロックには「ITL(Interested Transaction List)」があり、同時にそのブロックを更新できるトランザクション数を制限しています。デフォルトは2(テーブル)または2(インデックス)。
ブロック内ITLスロット: 2個
セッションA: 1スロット使用中
セッションB: 1スロット使用中
セッションC: 新規に取得しようとする → 待機
セッションD: 同じく待機
→ 4つで循環依存 → デッドロック
ITLデッドロックの特定
トレースファイルに以下が記載されます:
Deadlock graph:
---------Blocker(s)-------- ---------Waiter(s)---------
Resource Name process session holds waits process session holds waits
TX-0006001a-0000abcd-00000000-00000000 45 123 X 46 124 S
TX-0006001b-0000abce-00000000-00000000 46 124 X 45 123 S
ロックタイプが S(Shared)の場合、ITLデッドロックの可能性が高いです。
対処:INITRANS の増加
-- 既存テーブルのINITRANSを増やす(既存ブロックには反映されない)
ALTER TABLE big_table INITRANS 8;
-- 既存ブロックも更新するにはMOVE
ALTER TABLE big_table MOVE INITRANS 8;
-- インデックスのREBUILD必要
ALTER INDEX idx_big_table_xxx REBUILD;
INITRANS の推奨値
| 同時更新セッション数 | 推奨INITRANS |
|---|---|
| 〜2 | デフォルト(2) |
| 3〜5 | 4 |
| 6〜10 | 8 |
| 高並列バッチ | 16〜32 |
ただし、INITRANSを大きくすると各ブロックの有効データ領域が減少するため、過剰に大きくしない方が良いです。
【原因④】ビットマップインデックスの並行更新
ビットマップインデックスは、複数行のキー値を1つのビットマップエントリにまとめます。これが並行更新で衝突しやすい構造です。
確認
SELECT INDEX_NAME, TABLE_NAME, INDEX_TYPE
FROM USER_INDEXES
WHERE INDEX_TYPE = 'BITMAP';
対処
- OLTP系(並行更新が多い)テーブルからビットマップを排除
- ビットマップはDWH・読み取り中心のテーブルに限定
- 必要に応じてB-Treeインデックスに変更
DROP INDEX bitmap_idx;
CREATE INDEX btree_idx ON my_table(my_column); -- 標準B-Tree
【原因⑤】MAX+1方式での連番採番
「現在の最大値+1を新IDにする」採番方式は、並行INSERTでデッドロックを引き起こします。
問題のあるコード
INSERT INTO orders (order_id, ...)
VALUES (
(SELECT MAX(order_id) + 1 FROM orders), -- 並行で同じ値を取る
...
);
複数セッションが同じMAX+1値を取ろうとし、PK制約違反 → リトライ → さらにデッドロック という負の連鎖になります。
対処:シーケンス利用
-- シーケンス作成
CREATE SEQUENCE orders_seq START WITH 1 INCREMENT BY 1 CACHE 100;
-- 採番
INSERT INTO orders (order_id, ...)
VALUES (orders_seq.NEXTVAL, ...);
12c以降はIDENTITY列でさらに簡潔に書けます:
CREATE TABLE orders (
order_id NUMBER GENERATED ALWAYS AS IDENTITY,
...
);
シーケンスとIDENTITYはデッドロックを起こさない設計になっています。
トレースファイルの解析
デッドロック発生時、Oracleは自動でトレースファイルを生成します。これを読むことで根本原因を特定できます。
alert.log の確認
# alert.logの場所
SELECT VALUE FROM V$DIAG_INFO WHERE NAME = 'Diag Alert';
# 典型的なalert.logエントリ
ORA-00060: Deadlock detected. More info in file /u01/app/oracle/diag/rdbms/orcl/orcl/trace/orcl_ora_12345.trc.
トレースファイルの場所
SELECT VALUE FROM V$DIAG_INFO WHERE NAME = 'Diag Trace';
-- 例: /u01/app/oracle/diag/rdbms/orcl/orcl/trace
トレースファイルの主要セクション
*** 2026-06-15 10:23:45.123
ORA-00060: Deadlock detected. See Note 60.1 at My Oracle Support.
Deadlock graph:
---------Blocker(s)-------- ---------Waiter(s)---------
Resource Name process session holds waits process session holds waits
TX-00050015-000001a3-00000000-00000000 25 145 X 26 147 X
TX-00060021-000001b8-00000000-00000000 26 147 X 25 145 X
session 145: DID 0001-0019-0000003F session 147: DID 0001-001A-00000041
session 147: DID 0001-001A-00000041 session 145: DID 0001-0019-0000003F
Rows waited on:
Session 145: obj - rowid = 00012345 - AAAB17AAEAAAACWAAB
(dictionary objn - 74565, file - 4, block - 150, slot - 1)
Session 147: obj - rowid = 00012346 - AAAB17AAEAAAACWAAC
(dictionary objn - 74566, file - 4, block - 150, slot - 2)
Current SQL statement for this session:
UPDATE employee SET salary = :1 WHERE emp_id = :2
----- PL/SQL Call Stack -----
object line object
handle number name
0x12345678 42 procedure SCOTT.UPDATE_SALARY
読み方のポイント
- Deadlock graph: どのセッションが何を持って何を待っているか
- Rows waited on: 競合の対象行(ROWID)
- Current SQL: その時実行中だったSQL
- PL/SQL Call Stack: PL/SQL呼び出し階層
関与オブジェクトの特定
-- objn から実際のオブジェクト名を取得
SELECT OWNER, OBJECT_NAME, OBJECT_TYPE
FROM DBA_OBJECTS
WHERE OBJECT_ID IN (74565, 74566);
ロックタイプの解読
| Lock Mode | 略 | 意味 |
|---|---|---|
| Null | N | ヌル |
| Row-S | SS | 行共有 |
| Row-X | SX | 行排他 |
| Share | S | 共有 |
| S/Row-X | SSX | 共有/行排他 |
| Exclusive | X | 排他 |
通常のデッドロックは X-X、ITLデッドロックは X-S が多いです。
デッドロック発生時のロック競合確認
リアルタイムでロック状況を確認するためのSQL集です。
現在ブロックされているセッション
SELECT
blocking.SID AS BLOCKING_SID,
blocking.SERIAL# AS BLOCKING_SERIAL,
blocking.USERNAME AS BLOCKING_USER,
blocked.SID AS BLOCKED_SID,
blocked.SERIAL# AS BLOCKED_SERIAL,
blocked.USERNAME AS BLOCKED_USER,
blocked.SQL_ID AS BLOCKED_SQL_ID,
blocked.EVENT
FROM V$SESSION blocking
JOIN V$SESSION blocked
ON blocked.BLOCKING_SESSION = blocking.SID
WHERE blocked.BLOCKING_SESSION IS NOT NULL;
DBA_BLOCKERS / DBA_WAITERS
-- ブロック側
SELECT * FROM DBA_BLOCKERS;
-- 待機側
SELECT * FROM DBA_WAITERS;
V$LOCK の詳細
SELECT
l.SID,
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,
l.BLOCK, -- 1: ブロッカー
s.SQL_ID,
s.STATUS,
s.MACHINE,
s.PROGRAM
FROM V$LOCK l
JOIN V$SESSION s ON l.SID = s.SID
WHERE l.TYPE IN ('TX', 'TM')
ORDER BY l.SID;
ロック対象の行・テーブル特定
-- TX ロックの対象行
SELECT
OWNER,
OBJECT_NAME,
SUBOBJECT_NAME,
OBJECT_TYPE
FROM DBA_OBJECTS
WHERE OBJECT_ID IN (
SELECT ID1 FROM V$LOCK WHERE TYPE = 'TM'
);
ブロッカーの強制終了(最終手段)
業務影響を慎重に判断した上で:
ALTER SYSTEM KILL SESSION 'SID,SERIAL#' IMMEDIATE;
-- 例
ALTER SYSTEM KILL SESSION '145,12345' IMMEDIATE;
アプリケーション側の対処パターン
ORA-00060はDB側の調整だけでなく、アプリ側の実装でも大幅に減らせます。
パターン1:リトライロジック
デッドロックは Oracle が片方のセッションをロールバックして解消するため、アプリ側でリトライするのが基本対処です。
public void updateWithRetry(int empId, double newSalary) throws SQLException {
int maxRetries = 3;
int retryDelay = 100; // milliseconds
for (int attempt = 0; attempt < maxRetries; attempt++) {
try {
// 通常処理
PreparedStatement ps = conn.prepareStatement(
"UPDATE employee SET salary = ? WHERE emp_id = ?"
);
ps.setDouble(1, newSalary);
ps.setInt(2, empId);
ps.executeUpdate();
conn.commit();
return; // 成功
} catch (SQLException e) {
if (e.getErrorCode() == 60 && attempt < maxRetries - 1) {
// ORA-00060 デッドロック → リトライ
conn.rollback();
Thread.sleep(retryDelay * (attempt + 1)); // 指数的バックオフ
continue;
}
throw e; // 他のエラー or 最終試行
}
}
}
パターン2:SELECT FOR UPDATE NOWAIT
ロック待機を避けて、即座にエラー応答:
SELECT * FROM employee WHERE emp_id = 1 FOR UPDATE NOWAIT;
-- ロック取得失敗時: ORA-00054
アプリ側で ORA-00054 をハンドリングし、リトライまたはユーザーに通知する設計に。
パターン3:FOR UPDATE WAIT n(時間制限付き)
SELECT * FROM employee WHERE emp_id = 1 FOR UPDATE WAIT 5;
-- 5秒待機して取れなければ: ORA-30006
パターン4:楽観的ロック(バージョン番号方式)
-- テーブルにバージョン列追加
ALTER TABLE employee ADD (version NUMBER DEFAULT 0);
-- UPDATE時にバージョンチェック
UPDATE employee
SET salary = :new_salary, version = version + 1
WHERE emp_id = :emp_id AND version = :current_version;
-- 0行更新ならコンフリクト
IF SQL%ROWCOUNT = 0 THEN
-- 他セッションが先に更新済み → リフェッチして再試行
...
END IF;
楽観的ロックはロックを長時間保持しない設計で、デッドロックを根本的に避けられます。
パターン5:トランザクションの短縮
-- ❌ NG:長すぎるトランザクション
BEGIN
-- 何百件もの更新
-- 業務ロジック
-- 外部API呼び出し
-- さらに更新
COMMIT;
END;
-- ✅ OK:小さく分けたトランザクション
BEGIN
-- 必要最小限の更新
COMMIT;
END;
-- 業務ロジック・外部API
BEGIN
-- 次の更新
COMMIT;
END;
トランザクションが短ければロック保持時間も短く、デッドロックの可能性が大幅に減ります。
各言語でのエラーコード判定
Python
import oracledb
import time
def update_with_retry(conn, sql, params, max_retries=3):
for attempt in range(max_retries):
try:
cursor = conn.cursor()
cursor.execute(sql, params)
conn.commit()
return
except oracledb.DatabaseError as e:
error, = e.args
if error.code == 60 and attempt < max_retries - 1:
conn.rollback()
time.sleep(0.1 * (attempt + 1))
continue
raise
Node.js
async function updateWithRetry(conn, sql, params, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
await conn.execute(sql, params);
await conn.commit();
return;
} catch (err) {
if (err.errorNum === 60 && attempt < maxRetries - 1) {
await conn.rollback();
await new Promise(r => setTimeout(r, 100 * (attempt + 1)));
continue;
}
throw err;
}
}
}
.NET
public void UpdateWithRetry(OracleConnection conn, string sql, int maxRetries = 3)
{
for (int attempt = 0; attempt < maxRetries; attempt++) {
try {
using var cmd = new OracleCommand(sql, conn);
cmd.ExecuteNonQuery();
// commit
return;
} catch (OracleException ex) when (ex.Number == 60 && attempt < maxRetries - 1) {
Thread.Sleep(100 * (attempt + 1));
continue;
}
}
}
デッドロック予防のベストプラクティス
設計時から意識すべきポイントを整理します。
1. すべての外部キー列にインデックスを付ける
これだけで多くのデッドロックが防げます。設計レビュー時の必須チェック項目に。
2. アクセス順序の規約化
複数テーブル・複数行を更新する処理では、ID順・親→子順などの順序ルールを規約化。
3. トランザクションを短く保つ
外部API呼び出し・長時間処理をトランザクション内に入れない。必要最小限のDB操作だけに。
4. 適切なISOLATIONレベル
OracleはデフォルトでREAD COMMITTED。一般業務はこれで十分。SERIALIZABLEはデッドロック頻度が大幅に上がるため、本当に必要な箇所のみ使用。
5. ロックの粒度を意識
-- 行ロックで済むケース
UPDATE employee SET salary = ... WHERE emp_id = 1;
-- テーブルロックになるケース(避ける)
LOCK TABLE employee IN EXCLUSIVE MODE;
6. INITRANS設計
並行更新が多いテーブルはINITRANSを4〜8に設定。
7. 楽観的ロックの活用
「読み取り→ユーザー判断→更新」のような長時間処理は楽観的ロックで実装。
8. シーケンス・IDENTITYの利用
MAX+1方式は絶対に避ける。
9. 監視・ログ取得
-- 直近のデッドロック発生回数(要AWR)
SELECT
TO_CHAR(BEGIN_INTERVAL_TIME, 'YYYY-MM-DD HH24') AS HOUR,
SUM(DELTA_VALUE) AS DEADLOCKS
FROM DBA_HIST_SYSSTAT
JOIN DBA_HIST_SNAPSHOT USING (SNAP_ID, INSTANCE_NUMBER)
WHERE STAT_NAME = 'enqueue deadlocks'
AND BEGIN_INTERVAL_TIME > SYSDATE - 7
GROUP BY TO_CHAR(BEGIN_INTERVAL_TIME, 'YYYY-MM-DD HH24')
ORDER BY HOUR DESC;
10. アプリ全体のリトライ標準化
デッドロックは完全には消えません。リトライロジックを標準ライブラリ化して、各実装で必ず使う運用に。
関連エラーと違い
ORA-00054: resource busy and acquire with NOWAIT specified
NOWAIT指定でロック取得失敗。デッドロックではなく単なるロック競合です。
ORA-30006: resource busy; acquire with WAIT timeout expired
WAIT n指定での待機タイムアウト。同じく単なるロック競合。
ORA-04068: existing state of packages has been discarded
パッケージステート関連のエラー。デッドロックとは無関係。
ORA-00060の関連メッセージ
ORA-00060: deadlock detected while waiting for resource
ORA-00060: resource busy and acquire with NOWAIT specified or timeout expired
エラーメッセージは状況によって若干異なります。
トラブルシューティング・チェックリスト
ORA-00060が出た時に上から順にチェックする手順です。
- alert.logでトレースファイル特定
- トレースファイルの Deadlock graph と Current SQL 確認
- 競合対象のオブジェクトID → 実テーブル名特定
- 外部キー列のインデックス有無確認(最頻出原因)
- アクセス順序の不一致がないかコード確認
- MAX+1方式での採番がないか確認
- ビットマップインデックスの並行更新がないか確認
- ITLデッドロックの場合はINITRANS増加
- アプリ側にリトライロジック実装
- 長期的にはロック範囲縮小・楽観的ロック移行
よくある質問(FAQ)
Q1. デッドロックは完全に防げますか?
理論的にはゼロにすることは難しいです。データベースは並行アクセスを許容する設計のため、競合の余地が常にあります。ただし、本記事のベストプラクティスを守れば頻度を実用上問題ないレベルまで下げられます。
Q2. ORA-00060が出た時、データはどうなりますか?
Oracleが片方のセッションを自動ロールバックします。そのセッションの未コミットの変更は全て取り消され、もう一方のセッションは処理を続行できます。アプリ側ではエラーを受けてリトライするのが基本対処です。
Q3. トレースファイルが見つかりません
SELECT VALUE FROM V$DIAG_INFO WHERE NAME = 'Diag Trace';
で出るパス配下にあります。ファイル名はalert.logに記載されています。adrciコマンドでも一覧表示できます:
adrci> show tracefile -t %deadlock%
Q4. 外部キー列にインデックスを付けたら逆に遅くなりました
ありえます。インデックス追加はメンテナンスコスト(INSERT/DELETE時のオーバーヘッド)を伴います。子テーブルがINSERTが極めて多い場合は性能トレードオフを検討が必要。ただし、デッドロック回避の効果は大きいので、通常はインデックス追加を推奨します。
Q5. テーブル全体ロックが起きるのはなぜ?
考えられる原因:
LOCK TABLEが明示的に発行された- 大量UPDATE/DELETE時のロック範囲拡張
- DDL実行中(ALTER TABLE等)
- 外部キーチェックでの全行スキャン
V$LOCKのTMロック保有者を確認してください。
Q6. PL/SQLの中でORA-00060が出た時の挙動は?
PL/SQLブロックがロールバックされ、EXCEPTIONブロックでOTHERSまたは特定エラーを捕捉できます:
BEGIN
UPDATE ...
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE = -60 THEN
-- デッドロック処理
NULL;
END IF;
END;
PL/SQL内でリトライしたい場合はループで実装します。
Q7. デッドロックトレースが大量に出てディスクを圧迫します
adrciの自動クリーンアップを設定:
adrci> set control (SHORTP_POLICY = 720) # 30日
adrci> set control (LONGP_POLICY = 8760) # 1年
また、根本原因(FK未インデックス等)の対処も並行で進めてください。
Q8. 開発環境では再現せず本番だけで起きます
並行度の差が原因です。本番では多数ユーザーが同時操作するため競合が顕在化します。負荷試験ツール(JMeter等)で並行アクセスを再現するテストを実施してください。
Q9. SELECT FOR UPDATEはどう使い分けますか?
| 構文 | 用途 |
|---|---|
FOR UPDATE | 行を排他ロック、解放まで待機 |
FOR UPDATE NOWAIT | 取れなければ即エラー(ORA-00054) |
FOR UPDATE WAIT 5 | 5秒待ってダメならエラー(ORA-30006) |
FOR UPDATE SKIP LOCKED | ロック中の行はスキップ(ジョブキュー等) |
長時間ロックを避けたい場合はNOWAITまたはSKIP LOCKEDを活用。
Q10. RAC環境でデッドロックは増えますか?
RAC(Real Application Clusters)ではグローバルなロック調整が必要なため、シングルインスタンスより検出に若干時間がかかることがあります。ただし発生頻度自体は設計次第。RAC環境では特に「ノード間でのアクセス順序統一」が重要です。
参考リンク・関連資料
Oracle公式ドキュメント
- Oracle Database Error Messages – ORA-00060 – 公式エラー解説
- Oracle Database Concepts – Data Concurrency and Consistency – 並行制御の概念
- Oracle Database Administrator’s Guide – Managing Locks – ロック管理
- Oracle Database Performance Tuning Guide – チューニングガイド
データディクショナリ・ビュー
トレースファイル分析
- Oracle Database Utilities – ADRCI – 診断データ管理
- Oracle Trace File Analyzer (TFA) – 診断ツール
My Oracle Support(要アカウント)
- MOS Note 62365.1: 「Understanding and Diagnosing ORA-00060 Errors」
- MOS Note 1559886.1: 「Master Note – How to Troubleshoot ORA-00060」
関連エラー記事(本サイト)
- ORA-01017: invalid username/password エラー対処 – 認証エラー
- ORA-12541: TNS no listener エラー対処 – リスナー接続エラー
- ORA-12154: TNS could not resolve エラー対処 – 接続識別子エラー
- ORA-00942: 表またはビューが存在しません エラー対処 – オブジェクト見つからずエラー
- ORA-01555: snapshot too old エラー対処 – UNDO切れエラー
関連Oracle記事(本サイト)
- [Oracleバージョン確認の方法]
- [Oracleユーザー一覧の取得方法]
- [Oracleテーブル一覧の取得方法]
- [Oracle directoryの確認方法]
まとめ
ORA-00060はOracleの並行制御メカニズムに起因するエラーで、設計レベルでの対処が本質解決です。要点を再整理します。
- 発生メカニズム: 複数セッションがリソースを互いに待ち続ける循環依存
- 最頻出原因: 外部キー列の未インデックス → FK列にインデックス追加で多くが解決
- アプローチ:
- アクセス順序の統一
- トランザクションの短縮
- シーケンス・IDENTITY利用
- ITLデッドロックには INITRANS 増加
- ビットマップインデックスは OLTP では避ける
- トレースファイル解析: Deadlock graph と Current SQL から原因特定
- アプリ側必須対応: リトライロジック・楽観的ロック・FOR UPDATE NOWAIT
- 継続的な監視:
V$LOCK、DBA_BLOCKERS、デッドロック発生回数
これらの知識は、Oracleの本番運用・並行処理設計・パフォーマンス改善などにおいてDBA・上級開発者には必須です。本記事をブックマークして、ORA-00060のトラブル対応と設計レビューのリファレンスとしてご活用ください。
本記事は2026年6月時点の情報をもとに、Oracle Database 19c / 21c / 23ai / 26ai での動作確認・公式ドキュメントに基づき作成しています。バージョンによっては挙動が異なる場合があるため、最新の情報はOracle公式ドキュメントもあわせてご確認ください。
-
前の記事
【完全版】ORA-00942: 表またはビューが存在しません エラーの原因と対処法 2026.06.16
-
次の記事
【完全版】MySQL「ERROR 1045 (28000): Access denied」の原因と対処法 2026.06.17
コメントを書く