【Oracle】ORA-02049の原因と解決方法|timeout: distributed transaction waiting for lock・分散トランザクションのロック待ち

【Oracle】ORA-02049の原因と解決方法|timeout: distributed transaction waiting for lock・分散トランザクションのロック待ち Oracle

ORA-02049: timeout: distributed transaction waiting for lock は、Oracleの分散トランザクションでロック待ちが長引き、distributed_lock_timeout の秒数を超えた時に発生するエラーです。DBリンク経由で別DBの表を更新する処理、複数DBにまたがるCOMMIT、リモートDB側の未コミット更新が絡む処理で起きやすいです。

通常のロック待ちと違い、ローカルDBだけを見ても原因が見えないことがあります。ローカルDBで待っているセッション、DBリンク先でロックを持っているセッション、分散トランザクションの状態を分けて確認します。

先に結論
ORA-02049が出たら、まず処理をROLLBACKして再試行できる状態に戻します。次に v$sessionv$lockdba_2pc_pending、DBリンク先DBのセッションを確認します。distributed_lock_timeout を伸ばす前に、未コミットトランザクションやロック順序、DBリンク先の処理遅延を疑います。
スポンサーリンク

ORA-02049とは

Oracle公式のエラー説明では、ORA-02049は distributed_lock_timeout 初期化パラメータで指定された秒数を超えて、ロックまたは分散トランザクション開始時の競合解消を待った時に発生します。対処としては、デッドロックのように扱い、トランザクションをロールバックして再試行することが示されています。

つまり、ORA-02049は「単に待てばよい」ではなく、分散トランザクションのどこかでロックが解放されないまま時間切れになったサインです。DBリンクの基本は データベースリンク完全ガイド、COMMIT/ROLLBACKの考え方は Oracleトランザクション完全ガイド も参考になります。

よくある発生パターン

DBリンク経由のUPDATE/DELETE

ローカルDBから UPDATE remote_table@dblink を実行し、リモート側で同じ行が未コミット更新中のケースです。

複数DBにまたがる処理

ローカル表とリモート表を同一トランザクション内で更新し、どちらかのDBでロック待ちが長引くケースです。

COMMIT/ROLLBACK漏れ

アプリケーション、SQLツール、バッチが未コミットのまま放置され、別セッションがリモート側で待ち続けるケースです。

ロック取得順序の不一致

処理Aはローカル→リモート、処理Bはリモート→ローカルの順に更新するなど、分散環境で競合しやすい順序になっているケースです。

DBリンク先DBの遅延

リモートDB側の長時間SQL、待機イベント、ネットワーク遅延により、ロック解放や2フェーズコミット処理が遅れるケースです。

distributed_lock_timeoutを確認する

まず、現在の待機時間設定を確認します。ただし、この値を大きくすれば解決するとは限りません。原因が未コミット放置やロック順序なら、単に失敗までの時間が延びるだけです。

check-distributed-lock-timeout.sql
SHOW PARAMETER distributed_lock_timeout;

SELECT name,
       value,
       isdefault,
       issys_modifiable
FROM v$parameter
WHERE name = 'distributed_lock_timeout';
値を伸ばす前に原因を探す
distributed_lock_timeout を大きくすると、利用者から見る待ち時間も長くなります。障害時は先にロック元セッション、未コミット処理、DBリンク先の状態を確認します。

どのDBリンクを使っているか確認する

ORA-02049では、まず対象のDBリンクと接続先DBを特定します。アプリケーションSQLに @DBLINK が見えていれば早いですが、セッション状態や DBA_DB_LINKS のDBリンク定義から確認できることもあります。

check-open-dblink.sql
SELECT db_link,
       owner_id,
       logged_on,
       heterogeneous,
       protocol,
       open_cursors,
       in_transaction
FROM v$dblink
ORDER BY db_link;
check-db-link-definition.sql
SELECT owner,
       db_link,
       username,
       host,
       created
FROM dba_db_links
WHERE db_link LIKE UPPER('%TARGET%')
ORDER BY owner, db_link;

V$DBLINK.IN_TRANSACTIONYES の場合、そのセッションがDBリンクを使ったトランザクション中である可能性があります。接続先の HOST やDBリンク名から、どのリモートDBを調査するかを決めます。

ローカルDB側の待機セッションを確認する

ORA-02049が出たセッションの周辺で、ローカルDB側に待機やブロッキング情報が残っていないか確認します。blocking_session が取れない場合もありますが、イベント、SQL ID、DBリンクを使うSQLを確認します。

check-local-waiting-sessions.sql
SELECT sid,
       serial#,
       username,
       machine,
       program,
       status,
       event,
       blocking_session,
       seconds_in_wait,
       sql_id
FROM v$session
WHERE username IS NOT NULL
ORDER BY seconds_in_wait DESC;
check-sql-using-dblink.sql
SELECT s.sid,
       s.serial#,
       s.username,
       s.sql_id,
       q.sql_text
FROM v$session s
JOIN v$sql q
  ON q.sql_id = s.sql_id
WHERE q.sql_text LIKE '%@%'
ORDER BY s.sid;

通常のセッション確認や強制切断の基本は セッションの確認・強制切断方法 も参考になります。

ロック情報を確認する

ローカルDB側で見える範囲のロックを確認します。分散トランザクションではリモートDB側のロックが本体のこともあるため、このSQLだけで完結しない点に注意します。

check-local-locks.sql
SELECT s.sid,
       s.serial#,
       s.username,
       s.machine,
       s.program,
       l.type,
       l.id1,
       l.id2,
       l.lmode,
       l.request,
       l.block
FROM v$lock l
JOIN v$session s
  ON s.sid = l.sid
WHERE l.request > 0 OR l.block > 0
ORDER BY l.block DESC, s.sid;

通常のロック待ち、DDLロック、デッドロックとの違いも見ます。ORA-00054ORA-00060ORA-04021 の記事も切り分けに使えます。

DBリンク先のセッションを確認する

ORA-02049では、DBリンク先DBでロックを持っているセッションを確認することが重要です。ローカルDBだけで原因が分からない場合、リモートDBへログインして同じ時間帯のセッション、SQL、ロックを確認します。

check-remote-sessions.sql
-- DBリンク先DBで実行する
SELECT sid,
       serial#,
       username,
       machine,
       program,
       module,
       status,
       event,
       blocking_session,
       last_call_et,
       sql_id
FROM v$session
WHERE username IS NOT NULL
ORDER BY last_call_et DESC;
check-remote-locked-objects.sql
-- DBリンク先DBで実行する
SELECT s.sid,
       s.serial#,
       s.username,
       s.machine,
       s.program,
       o.owner,
       o.object_name,
       o.object_type,
       lo.locked_mode
FROM v$locked_object lo
JOIN dba_objects o
  ON o.object_id = lo.object_id
JOIN v$session s
  ON s.sid = lo.session_id
ORDER BY s.last_call_et DESC;

リモート側で未コミットの更新セッションが見つかった場合、業務影響を確認してCOMMIT/ROLLBACK、ジョブ停止、またはセッション整理を検討します。

分散トランザクションの未解決状態を確認する

ネットワーク断や障害が絡むと、分散トランザクションが未解決の状態として残ることがあります。DBA_2PC_PENDING でin-doubtトランザクションの有無を確認します。

check-2pc-pending.sql
SELECT local_tran_id,
       global_tran_id,
       state,
       mixed,
       advice,
       tran_comment,
       fail_time,
       force_time,
       retry_time
FROM dba_2pc_pending
ORDER BY fail_time DESC;
check-2pc-neighbors.sql
SELECT local_tran_id,
       in_out,
       database,
       dbuser_owner,
       interface
FROM dba_2pc_neighbors
ORDER BY local_tran_id, in_out;
check-pending-trans-details.sql
SELECT p.local_tran_id,
       p.state,
       p.mixed,
       p.fail_time,
       n.in_out,
       n.database,
       n.dbuser_owner,
       n.interface
FROM dba_2pc_pending p
LEFT JOIN dba_2pc_neighbors n
  ON n.local_tran_id = p.local_tran_id
ORDER BY p.fail_time DESC, n.in_out;
FORCE COMMIT/ROLLBACKは最後の手段
COMMIT FORCEROLLBACK FORCE は、分散トランザクションの整合性に関わる操作です。実行前に関係DBの状態、業務影響、トランザクションの状態、Oracleサポートや運用手順を確認します。
force-resolution-example.sql
-- 例: 運用手順で明確に判断できる場合だけ実行する
-- COMMIT FORCE 'local_tran_id';
-- ROLLBACK FORCE 'local_tran_id';

-- 実行後はDBA_2PC_PENDINGで状態を再確認する
SELECT local_tran_id, state, mixed, force_time
FROM dba_2pc_pending
ORDER BY fail_time DESC;

再試行設計を入れる

Oracle公式でも、ORA-02049はデッドロックのように扱い、トランザクションをロールバックして再試行する方針が示されています。アプリケーション側では、同じトランザクションを握ったまま再実行するのではなく、一度ROLLBACKしてから短い待機を挟んで再試行します。

retry-policy.txt
ORA-02049発生時の考え方

1. 現在のトランザクションをROLLBACKする
2. 短い待機を入れる
3. 同じ処理を再試行する
4. 再試行回数を超えたら業務エラーとして扱う
5. ログにローカルDB、DBリンク名、SQL ID、対象キーを残す

再試行しても頻発する場合は、処理順序、更新対象キー、DBリンク先の長時間トランザクション、接続プールやバッチ多重起動を見直します。

ロック順序をそろえる

分散トランザクションでは、複数の処理が別々の順序でローカル表とリモート表を更新すると、ロック待ちが起きやすくなります。可能なら、すべての処理でロック取得順序をそろえます。

lock-order-example.txt
悪い例:
  処理A: ローカル表 -> リモート表@DBLINK
  処理B: リモート表@DBLINK -> ローカル表

改善例:
  処理A: ローカル表 -> リモート表@DBLINK
  処理B: ローカル表 -> リモート表@DBLINK

同じ順序でロックを取ることで、相互待ちやタイムアウトを減らす。

関連エラーとの違い

ORA-02049

分散トランザクションでロック待ちが distributed_lock_timeout を超えたエラーです。DBリンク先DBの確認が重要です。

ORA-00054

NOWAIT指定やDDL_LOCK_TIMEOUTに関連するロック取得失敗です。主にローカルDBのDDLやリソース待ちで見ます。

ORA-00060

デッドロックです。複数セッションが互いのロック解放を待ち、Oracleが検出して片方を失敗させます。

ORA-04021

DDLやコンパイルでオブジェクトロックを取得できずタイムアウトするエラーです。ライブラリキャッシュやDDLロック待ちを見ます。

ORA-02050 / ORA-02068

分散トランザクションやDBリンク先の障害で一緒に確認することがあります。ローカルDBだけでなくリモートDB側のログも見ます。

対応手順のまとめ

  1. ORA-02049が出た処理をROLLBACKし、再試行可能な状態に戻す
  2. distributed_lock_timeout の現在値を確認する
  3. V$DBLINK とDBリンク定義で、対象DBリンクと接続先DBを特定する
  4. ローカルDB側の v$sessionv$lock、SQL IDを確認する
  5. DBリンク先DBにログインして、リモート側のセッションとロックを確認する
  6. DBA_2PC_PENDING で未解決の分散トランザクションを確認する
  7. 未コミット放置、長時間SQL、ロック順序、バッチ多重起動を見直す
  8. アプリケーション側にROLLBACK後の再試行設計を入れる

よくある質問

distributed_lock_timeoutを伸ばせば直りますか?

一時的にエラーが出にくくなる可能性はありますが、根本対策ではありません。未コミット処理やDBリンク先のロックが原因なら、待ち時間が延びるだけです。

ローカルDBにロック元が見つかりません。

DBリンク先DBでロックを持っている可能性があります。リモートDBへログインし、同じ時間帯の v$sessionv$locked_object、実行SQLを確認します。

ORA-02049はデッドロックですか?

厳密にはORA-00060とは別のエラーですが、Oracle公式ではデッドロックのように扱い、ROLLBACKして再試行する対処が示されています。

COMMIT FORCEを使ってよいですか?

安易に使いません。in-doubtトランザクションの強制解決は整合性に影響します。関係DBの状態と運用手順を確認してから判断します。

ORA-02050やORA-02068も出ています。何を見ますか?

分散トランザクションやDBリンク先の障害が絡んでいる可能性があります。ローカルDBのエラーだけでなく、DBリンク先DBのアラートログ、セッション、未解決トランザクションを確認します。

参考