Oracle のすべてのセッションは、常に「CPU で処理中」か「何かを待機中」のどちらかです。待機イベント(Wait Events)は、セッションが何を待っているのかを記録する仕組みで、パフォーマンスのボトルネックが「ディスク I/O なのか・ロック競合なのか・COMMIT のログ書き込みなのか」を数値で把握できます。
チューニングの第一歩は「どこで時間を使っているか」を知ることです。待機イベントはその答えを直接示してくれます。
- 待機イベントの仕組みと WAIT_CLASS による分類
- V$SESSION_WAIT で現在の待機をリアルタイム確認する方法
- V$SYSTEM_EVENT でインスタンス全体の累積待機統計を分析する方法
- db file sequential/scattered read・log file sync・latch・enq の原因と対処
- CPU は待機イベントではない — Time Model との関係
- AWR レポートのトップ待機イベントセクションの読み方
待機イベントとは
セッションが処理を進めるために何かのリソースを必要として一時停止している状態を待機と呼び、その種類ごとに名前が付いたものが待機イベントです。Oracle は数百種類の待機イベントを定義しており、各イベントには原因を特定するための3つのパラメータ(P1・P2・P3)が付随します。
| WAIT_CLASS | 説明 | 代表的なイベント |
|---|---|---|
| User I/O | データファイルの読み書き(ユーザー要求によるI/O) | db file sequential read, db file scattered read |
| System I/O | バックグラウンドプロセスによるI/O | db file parallel write, log file parallel write |
| Concurrency | 内部リソース(ラッチ・ミューテックス)の競合 | latch: library cache, latch: cache buffers chains |
| Application | アプリケーションロジックによる待機(行ロックなど) | enq: TX – row lock contention |
| Commit | COMMIT 処理に関連する待機 | log file sync |
| Configuration | 設定パラメータ不足による待機 | log file switch (checkpoint incomplete) |
| Network | クライアントとの通信待機 | SQL*Net more data to client |
| Idle | 仕事がなく待機中(正常な状態) | SQL*Net message from client |
SQL*Net message from client(クライアントからのリクエスト待ち)などの Idle 待機は、セッションに仕事がない正常な状態を示します。トップ待機イベントを分析するときは Idle を除外して「本当のボトルネック」を探します。
V$SESSION_WAIT で現在の待機をリアルタイム確認する
-- V$SESSION_WAIT: 各セッションが今何を待っているかをリアルタイムで確認
SELECT
sw.sid,
sw.seq#, -- 待機シーケンス番号(増加するほど待機が発生している)
sw.event, -- 待機イベント名
sw.wait_class, -- 待機クラス(User I/O, Concurrency, Application, Idle など)
sw.p1text, -- P1 の意味(例: file# / lock type など)
sw.p1, -- P1 の値
sw.p2text, -- P2 の意味
sw.p2,
sw.p3text, -- P3 の意味
sw.p3,
sw.wait_time, -- 0 = 現在待機中 / 正の値 = 最後の待機時間(1/100秒)
sw.seconds_in_wait -- 現在の待機継続時間(秒)
FROM V$SESSION_WAIT sw
WHERE sw.wait_class != 'Idle' -- Idle 待機を除外
AND sw.seconds_in_wait > 0 -- 実際に待機しているセッションのみ
ORDER BY sw.seconds_in_wait DESC;
-- 誰が・何のSQLで・何を待っているかを一括確認
SELECT
s.sid,
s.serial#,
s.username,
s.program,
s.event,
s.wait_class,
s.seconds_in_wait,
s.sql_id,
SUBSTR(sq.sql_text, 1, 60) AS sql_preview
FROM V$SESSION s
LEFT JOIN V$SQL sq ON s.sql_id = sq.sql_id
WHERE s.wait_class != 'Idle'
AND s.status = 'ACTIVE'
ORDER BY s.seconds_in_wait DESC;
-- 特定の待機イベントに絞り込む(例: ロック待ち)
SELECT s.sid, s.username, s.event, s.seconds_in_wait, s.sql_id
FROM V$SESSION s
WHERE s.event LIKE 'enq: TX%' -- 行ロック競合
ORDER BY s.seconds_in_wait DESC;
V$SYSTEM_EVENT で累積統計を分析する
V$SESSION_WAIT は瞬間的なスナップショットですが、V$SYSTEM_EVENT はインスタンス起動からの累積待機統計を記録します。長期的なボトルネックを特定するのに適しています。
-- Idle を除いた累積待機時間 Top 20(インスタンス全体のボトルネック分析)
SELECT
event,
wait_class,
total_waits,
total_timeouts,
ROUND(time_waited / 100, 2) AS time_waited_sec, -- 1/100秒 → 秒に変換
ROUND(average_wait / 100, 3) AS avg_wait_sec, -- 平均待機時間(秒)
ROUND(time_waited / 100
/ NULLIF(total_waits, 0), 4) AS avg_per_wait_sec -- 1回あたりの待機時間
FROM V$SYSTEM_EVENT
WHERE wait_class != 'Idle'
ORDER BY time_waited DESC
FETCH FIRST 20 ROWS ONLY;
-- 特定の期間の待機を差分で取得する(スナップショット方式)
-- ① 計測開始時点のスナップショットをテーブルに保存
CREATE TABLE wait_snap AS
SELECT event, total_waits, time_waited FROM V$SYSTEM_EVENT WHERE wait_class != 'Idle';
-- ② 計測期間後に差分を確認
SELECT
cur.event,
cur.total_waits - NVL(snap.total_waits, 0) AS delta_waits,
ROUND((cur.time_waited - NVL(snap.time_waited, 0)) / 100, 2) AS delta_sec
FROM V$SYSTEM_EVENT cur
LEFT JOIN wait_snap snap ON cur.event = snap.event
WHERE cur.wait_class != 'Idle'
AND cur.total_waits - NVL(snap.total_waits, 0) > 0
ORDER BY delta_sec DESC
FETCH FIRST 20 ROWS ONLY;
主要待機イベント別の原因と対処法
db file sequential read(シングルブロック読み込み)
インデックスを使った検索(INDEX RANGE SCAN)でインデックスリーフブロックやTABLE ACCESS BY ROWID のデータブロックを1ブロックずつ読み込むときに発生します。P1=ファイル番号, P2=ブロック番号, P3=ブロック数(常に1)
- 正常な場合:インデックスを使って少数の行を取得している — このイベントは必ずしも悪くない
- 問題のある場合:非効率なインデックス(選択率が低い)を大量に参照している
- 対処:実行計画を確認し、全表スキャンの方が効率的なケースではヒント句で調整。バッファキャッシュを増やして物理読み込みを減らす
db file scattered read(マルチブロック読み込み)
全表スキャン(TABLE ACCESS FULL)や高速全索引スキャン(INDEX FAST FULL SCAN)で複数の連続ブロックをまとめて読み込むときに発生します。P1=ファイル番号, P2=先頭ブロック番号, P3=読み込みブロック数
- 正常な場合:大量データを処理する集計クエリなど — 全表スキャン自体は必ずしも悪くない
- 問題のある場合:インデックスを使うべき結合や絞り込みで全表スキャンが走っている
- 対処:適切なインデックス追加。DB_FILE_MULTIBLOCK_READ_COUNT の調整(大きくするほどマルチブロック I/O が効率化される)
-- 現在最も I/O 待機しているセッションを特定する
SELECT
s.sid,
s.username,
s.event,
s.seconds_in_wait,
s.sql_id,
DBMS_ROWID.ROWID_BLOCK_NUMBER(
DBMS_ROWID.ROWID_CREATE(1,
s.p1, -- ファイル番号
s.p2, -- ブロック番号(db file sequential read の P2)
0)
) AS block_num -- 待機中のブロック番号(参考)
FROM V$SESSION s
WHERE s.event IN ('db file sequential read', 'db file scattered read')
AND s.wait_time = 0 -- 現在待機中
ORDER BY s.seconds_in_wait DESC;
-- P1(ファイル番号)からどのテーブル・インデックスを読んでいるか確認
SELECT file_id, tablespace_name, file_name
FROM DBA_DATA_FILES
WHERE file_id = 4; -- P1 の値を指定
log file sync(COMMIT のREDO書き込み待ち)
COMMIT を実行するたびに、Oracle はそのトランザクションの REDO ログをLGWR(Log Writer)プロセスがオンラインREDOログファイルに書き込むのを待ちます。P1=書き込みを待っているREDO量(バイト)
- 原因:COMMIT 頻度が高すぎる(1行ずつ COMMIT)・REDOログがHDD上にある・ディスクI/Oが遅い
- 対処:①バッチ処理でのCOMMIT頻度を下げる(1000件ごとなど)、②REDOログファイルをSSDに移動、③ログファイルのサイズを大きくしてスイッチ頻度を下げる
enq: TX – row lock contention(行ロック競合)
別のセッションが UPDATE/DELETE でロックを保持している行を、別のセッションが更新しようとしたときに発生します。P1=ロックタイプとモード(TX, mode=6), P2=undo segment番号, P3=undo sequence番号
-- ロックを待っているセッションと保持しているセッションを特定
SELECT
wait.sid AS waiting_sid,
wait.username AS waiting_user,
wait.seconds_in_wait,
hold.sid AS blocking_sid,
hold.username AS blocking_user,
hold.status AS blocking_status,
SUBSTR(sq.sql_text, 1, 60) AS blocking_sql
FROM V$SESSION wait
JOIN V$SESSION hold ON wait.blocking_session = hold.sid
LEFT JOIN V$SQL sq ON hold.sql_id = sq.sql_id
WHERE wait.event LIKE 'enq: TX%'
ORDER BY wait.seconds_in_wait DESC;
-- ブロッキングセッションを強制終了する(緊急時)
-- ALTER SYSTEM KILL SESSION 'blocking_sid,blocking_serial#' IMMEDIATE;
latch: library cache(共有プール競合)
多数のセッションが同時にハードパース(SQL の新規解析)を行うと、ライブラリキャッシュのラッチを取得しようと競合します。バインド変数を使わないリテラル SQL が大量に実行されている環境で発生しやすいです。
- 対処:バインド変数の使用(根本解決)、CURSOR_SHARING=FORCE(暫定)、SHARED_POOL_SIZE の増設
「CPU 待ち」は待機イベントではない — Time Model との関係
CPU での処理時間は待機イベントとしては記録されません。Oracle では CPU 時間と待機時間を合わせた「DB time」で全体のパフォーマンスを計測します。
-- DB time(CPU時間 + 非アイドル待機時間)の内訳を確認
SELECT
stat_name,
ROUND(value / 1000000, 2) AS seconds -- マイクロ秒 → 秒に変換
FROM V$SYS_TIME_MODEL
WHERE stat_name IN (
'DB time', -- CPU + 非アイドル待機の合計
'DB CPU', -- CPU で処理した時間
'sql execute elapsed time', -- SQL実行にかかった時間
'parse time elapsed' -- SQL解析にかかった時間
)
ORDER BY seconds DESC;
-- チューニングの指針:
-- DB time の大半が DB CPU → CPU バウンド(SQL の論理的な最適化が有効)
-- DB time の大半が待機 → I/O・ロック・ラッチのボトルネック解消が有効
AWR レポートのトップ待機イベント読み方
AWR(Automatic Workload Repository)レポートの「Top 10 Foreground Events by Total Wait Time」セクションには、スナップショット期間中に最も待機時間が多かったイベントが表示されます。
-- 直近のAWRスナップショット間の待機イベント差分を取得
SELECT
e.event_name,
e.wait_class,
e.total_waits_fg - b.total_waits_fg AS delta_waits,
ROUND((e.time_waited_fg - b.time_waited_fg)
/ 100, 2) AS delta_sec,
ROUND((e.time_waited_fg - b.time_waited_fg)
/ NULLIF(e.total_waits_fg - b.total_waits_fg, 0)
/ 100, 4) AS avg_wait_sec
FROM DBA_HIST_SYSTEM_EVENT e
JOIN DBA_HIST_SYSTEM_EVENT b
ON e.event_name = b.event_name
AND e.snap_id = b.snap_id + 1 -- 隣接するスナップショット間の差分
AND e.instance_number = b.instance_number
WHERE e.wait_class != 'Idle'
AND e.snap_id = (SELECT MAX(snap_id) FROM DBA_HIST_SNAPSHOT)
ORDER BY delta_sec DESC
FETCH FIRST 15 ROWS ONLY;
まとめ
- V$SESSION_WAIT:今この瞬間にどのセッションが何を待っているかを確認するリアルタイム診断ツール
- V$SYSTEM_EVENT:インスタンス全体の累積待機を確認して長期的なボトルネックを特定する
- Idle 待機は除外:
SQL*Net message from clientなどは正常な待機なので分析から除外する - 代表的な待機の対処:db file sequential/scattered read → I/O 最適化・インデックス見直し。log file sync → COMMIT 頻度を下げ REDO ログを SSD へ。enq: TX → ロック保持セッションを特定して解消。latch: library cache → バインド変数使用
- CPU はイベントでない:
V$SYS_TIME_MODELの DB CPU で CPU 消費を確認する
待機イベントと合わせて 遅いSQLを特定する方法完全ガイド(V$SQL・AWR・ASH)も参照してください。行ロック競合(enq: TX)の調査は セッションの確認・強制切断方法完全ガイドの手順と組み合わせると効果的です。

