【Oracle】ORA-00054 完全ガイド|resource busy and acquire with NOWAIT の原因・DDL のロック待機・対処法まで解説

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 デッドロック完全ガイドも参照してください。