ORA-00054: resource busy and acquire with NOWAIT specified, or timeout expiredは、ロック中のリソース(テーブル・行)に対して NOWAIT オプションを使ってアクセスしようとしたとき、または DDL 実行時にロック競合が発生したときに発生します。
SELECT FOR UPDATE NOWAIT でロック中の行を即時取得しようとした場合と、ALTER TABLE などの DDL をロック保持中のセッションがある状態で実行した場合の2つのパターンが主な原因です。
この記事でわかること
- ORA-00054 が発生する2つの主なパターン(NOWAIT / DDL 競合)
- SELECT FOR UPDATE の WAIT / NOWAIT / SKIP LOCKED の使い分け
- DDL_LOCK_TIMEOUT で DDL の待機時間を設定する方法
- V$SESSION / V$LOCK でロックを保持しているセッションを特定する方法
- ロックを保持しているセッションを解放する手順
ORA-00054 が発生する2つのパターン
| パターン | 発生条件 | 対処法 |
|---|---|---|
| SELECT FOR UPDATE NOWAIT | ロック中の行に対して即時ロック取得を試みた | WAIT N 秒の待機指定・SKIP LOCKED で別行を処理・ロック保持セッションの解放 |
| DDL 実行時のロック競合 | ALTER TABLE・TRUNCATE などの DDL 実行時に対象テーブルを参照・更新しているセッションがある | DDL_LOCK_TIMEOUT を設定して待機・ロック保持セッションを解放 |
SELECT FOR UPDATE の WAIT / NOWAIT / SKIP LOCKED
WAIT / NOWAIT / SKIP LOCKED の使い分け
-- SELECT FOR UPDATE NOWAIT: ロック取得できなければ即 ORA-00054 を返す SELECT employee_id, salary FROM employees WHERE department_id = 60 FOR UPDATE NOWAIT; -- ロック中なら即エラー -- ORA-00054: resource busy and acquire with NOWAIT specified, or timeout expired -- SELECT FOR UPDATE WAIT N: 最大 N 秒待機してからあきらめる SELECT employee_id, salary FROM employees WHERE department_id = 60 FOR UPDATE WAIT 5; -- 5秒待ってもロック取得できなければ ORA-00054 -- タイムアウト秒数は整数で指定(小数不可) -- SELECT FOR UPDATE(待機指定なし): ロックが解放されるまで無限に待機する SELECT employee_id, salary FROM employees WHERE department_id = 60 FOR UPDATE; -- ロックが解放されるまで待機(デッドロックのリスクに注意) -- SELECT FOR UPDATE SKIP LOCKED: ロック中の行をスキップして処理する -- キュー処理・ジョブ管理など「空いている行を取得して処理する」用途に最適 SELECT employee_id, salary FROM employees WHERE department_id = 60 FOR UPDATE SKIP LOCKED; -- ロック中の行は結果に含まれない → 他のセッションが処理していない行のみを処理できる -- SKIP LOCKED を使ったキュー処理パターン SELECT job_id, job_data FROM job_queue WHERE status = 'PENDING' AND ROWNUM <= 10 -- バッチサイズ FOR UPDATE SKIP LOCKED; -- 他のセッションが処理中のジョブはスキップ
DDL 実行時のロック競合と DDL_LOCK_TIMEOUT
DDL(ALTER TABLE・TRUNCATE TABLE・DROP TABLE など)を実行するには、対象テーブルへのすべての DML・SELECT が完了している必要があります。未コミットのトランザクションがある場合、DDL は待機するか ORA-00054 を発生させます。
DDL_LOCK_TIMEOUT で待機時間を設定する
-- DDL_LOCK_TIMEOUT: DDL がロック待機する最大秒数(デフォルト: 0 = 即エラー) -- 0 の場合: ロックが取得できなければ即 ORA-00054 -- セッション単位で DDL の待機時間を設定する ALTER SESSION SET DDL_LOCK_TIMEOUT = 30; -- 最大 30秒待機してから DDL を実行する -- システム全体に設定する(DBA権限) ALTER SYSTEM SET DDL_LOCK_TIMEOUT = 30; -- 設定後は DDL がロック待機するようになる(接続セッションがある状態でも待機する) ALTER TABLE employees ADD column_new VARCHAR2(100); -- ロックが取得できない場合 → 30秒待機 → それでも取得できなければ ORA-00054 -- DDL の前にロック保持セッションを確認する(後述の方法で特定してから DDL を実行) -- 本番環境でのメンテナンス作業: 低負荷の時間帯 + DDL_LOCK_TIMEOUT で余裕を持たせる ALTER SESSION SET DDL_LOCK_TIMEOUT = 300; -- 5分待機 ALTER TABLE employees ADD salary_grade NUMBER;
ロックを保持しているセッションを特定して解放する
V$SESSION / V$LOCK でロックセッションを特定する
-- ORA-00054 が発生した場合: ロックを保持しているセッションを特定する
-- ステップ1: ロックを保持しているセッションを確認する
SELECT
s.sid,
s.serial#,
s.username,
s.status,
s.sql_id,
s.machine,
s.program,
ROUND(s.last_call_et / 60, 1) AS idle_min, -- アイドル時間(分)
s.logon_time
FROM V$SESSION s
WHERE s.status = 'ACTIVE'
OR s.sid IN (
SELECT blocking_session FROM V$SESSION WHERE blocking_session IS NOT NULL
)
ORDER BY idle_min DESC;
-- ステップ2: DML ロックの詳細を確認する(どのオブジェクトをロックしているか)
SELECT
s.sid,
s.username,
l.type AS lock_type, -- TM: テーブルロック, TX: トランザクションロック
l.mode_held AS held, -- ロックモード(0=なし,2=行共有,3=行排他,4=共有,5=共有行排他,6=排他)
l.mode_requested AS requested,
o.object_name AS locked_object,
o.object_type
FROM V$LOCK l
JOIN V$SESSION s ON l.sid = s.sid
LEFT JOIN DBA_OBJECTS o ON l.id1 = o.object_id
WHERE l.block = 1 -- ブロッカー(他のセッションをブロックしている)
ORDER BY s.username, o.object_name;
-- ステップ3: ロックを保持しているセッションを切断する(DBA権限)
-- ※ 切断はビジネスへの影響を確認してから実施する
ALTER SYSTEM KILL SESSION '42,1234'; -- SID=42, SERIAL#=1234
-- IMMEDIATE: すぐに切断(KILL SESSION のデフォルトは待機あり)
ALTER SYSTEM KILL SESSION '42,1234' IMMEDIATE;
よくある発生シナリオと対処法
| シナリオ | 原因 | 対処法 |
|---|---|---|
| アプリのデプロイ中に ALTER TABLE が ORA-00054 | 古いアプリのコネクションプールが対象テーブルの未コミットトランザクションを保持 | DDL_LOCK_TIMEOUT を設定してデプロイ・古いコネクションを強制切断 |
| バッチ処理中に TRUNCATE TABLE が ORA-00054 | バッチ処理が同一テーブルを参照している | バッチ処理の完了を待ってから TRUNCATE・またはスケジュールを調整 |
| SELECT FOR UPDATE NOWAIT が返す ORA-00054 | 他のセッションが同じ行に対して FOR UPDATE を保持している | WAIT 秒数付きに変更・SKIP LOCKED でスキップ・ロック保持セッションを調査 |
| 長時間アイドルのセッションがロックを保持 | アプリがコミットせずに接続を保持(接続プールの問題) | IDLE_TIME プロファイルで接続タイムアウトを設定・アプリのトランザクション管理を改善 |
まとめ
- NOWAIT:ロック取得できなければ即 ORA-00054。WAIT N で秒数指定の待機に変更できる
- SKIP LOCKED:ロック中の行をスキップして残りの行を処理する。キュー処理・並列ジョブ管理に最適
- DDL_LOCK_TIMEOUT:DDL 実行時のロック待機時間を設定する。デフォルト 0(即エラー)から秒数を指定することでデプロイ中の DDL が成功しやすくなる
- ロックの特定:V$LOCK で lock_type=TM(テーブルロック)・mode_held の確認・blocking_session で「誰が誰をブロックしているか」を把握する
- セッション切断:ALTER SYSTEM KILL SESSION ‘SID,SERIAL#’ IMMEDIATE で強制切断。ビジネス影響を確認してから実施する
ロック競合の詳細な調査方法は Oracle セッションロック完全ガイドを参照してください。デッドロック(ORA-00060)の対処については Oracle デッドロック完全ガイドも参照してください。