Oracle データベースでパフォーマンス問題が発生したとき、最初に行うべきは「どの SQL が遅いのか」を特定することです。闇雲にインデックスを追加したりパラメータを変更するのではなく、ボトルネックの SQL を正確に見つけてから対処することで効率的にチューニングできます。
本記事では、V$SQL でのTop N 分析、V$SESSION で現在実行中の重い SQL をリアルタイム特定、AWR / ASH / ADDM での履歴分析、そしてStandard Edition 向けの Statspack まで、段階的なアプローチで解説します。
・V$SQL で経過時間 / CPU / I/O の Top N SQL を特定するクエリ
・V$SESSION で現在実行中の重い SQL をリアルタイムで特定する方法
・V$SQL_MONITOR で長時間 SQL の進捗を確認する方法
・AWR レポートの読み方(SQL ordered by Elapsed Time)
・ASH(Active Session History)で待機イベントを分析する方法
・ADDM の自動推奨レポート
・Standard Edition 向けの Statspack
・段階的アプローチ(今 → 過去 → 定期)
遅い SQL 特定の段階的アプローチ
| 段階 | 状況 | 使うツール | 対象 |
|---|---|---|---|
| (1) リアルタイム | 今まさにシステムが遅い | V$SESSION / V$SQL_MONITOR | 現在実行中の SQL |
| (2) 直近の履歴 | 「さっき遅かった」SQL を特定したい | V$SQL / V$ACTIVE_SESSION_HISTORY | 共有プール内の SQL 統計 |
| (3) 過去の履歴 | 昨日 / 先週の遅い SQL を調べたい | AWR / ASH レポート | スナップショット間の統計 |
| (4) 定期分析 | 継続的にパフォーマンスを監視 | ADDM / Statspack | 自動推奨 / 定期レポート |
V$SQL で Top N SQL を特定する
V$SQL は Oracle の共有プール内の全 SQL 統計を保持するビューです。経過時間・CPU 時間・I/O 回数・実行回数などでソートして「最もリソースを消費している SQL」を特定します。
経過時間(Elapsed Time)の Top 10
SELECT * FROM (
SELECT
sql_id,
ROUND(elapsed_time / 1000000, 2) AS elapsed_sec,
executions,
ROUND(elapsed_time / NULLIF(executions, 0) / 1000000, 2) AS avg_sec,
ROUND(cpu_time / 1000000, 2) AS cpu_sec,
disk_reads,
buffer_gets,
SUBSTR(sql_text, 1, 100) AS sql_text_short
FROM v$sql
ORDER BY elapsed_time DESC
)
WHERE ROWNUM <= 10;
CPU 時間の Top 10
SELECT * FROM (
SELECT sql_id,
ROUND(cpu_time/1000000, 2) AS cpu_sec,
executions,
ROUND(cpu_time/NULLIF(executions,0)/1000000, 2) AS avg_cpu_sec,
SUBSTR(sql_text, 1, 100) AS sql_text_short
FROM v$sql
ORDER BY cpu_time DESC
)
WHERE ROWNUM <= 10;
ディスク読み取り(I/O)の Top 10
SELECT * FROM (
SELECT sql_id,
disk_reads,
executions,
ROUND(disk_reads/NULLIF(executions,0)) AS avg_disk_reads,
buffer_gets,
SUBSTR(sql_text, 1, 100) AS sql_text_short
FROM v$sql
ORDER BY disk_reads DESC
)
WHERE ROWNUM <= 10;
1 回あたりの実行時間が長い SQL
SELECT * FROM (
SELECT sql_id,
executions,
ROUND(elapsed_time/NULLIF(executions,0)/1000000, 2) AS avg_elapsed_sec,
ROUND(cpu_time/NULLIF(executions,0)/1000000, 2) AS avg_cpu_sec,
ROUND(buffer_gets/NULLIF(executions,0)) AS avg_buffer_gets,
SUBSTR(sql_text, 1, 100) AS sql_text_short
FROM v$sql
WHERE executions > 0
ORDER BY elapsed_time/executions DESC
)
WHERE ROWNUM <= 10;
V$SQL の主要列
| 列 | 単位 | 説明 |
|---|---|---|
| sql_id | ― | SQL の一意識別子(EXPLAIN PLAN や AWR で使用) |
| elapsed_time | マイクロ秒 | 総経過時間(待機含む) |
| cpu_time | マイクロ秒 | CPU 使用時間 |
| executions | 回 | 実行回数 |
| disk_reads | ブロック | 物理ディスク読み取りブロック数 |
| buffer_gets | ブロック | 論理読み取り(バッファキャッシュ)ブロック数 |
| rows_processed | 行 | 処理された行数 |
| sql_text | ― | SQL テキスト(先頭 1000 文字。全文は V$SQLTEXT) |
-- V$SQL の sql_text は先頭 1000 文字のみ -- 完全なテキストは V$SQLTEXT で取得 SELECT sql_text FROM v$sqltext WHERE sql_id = 'abc123def456' ORDER BY piece; -- または V$SQL_FULLTEXT(CLOB) SELECT sql_fulltext FROM v$sql WHERE sql_id = 'abc123def456';
V$SESSION で現在実行中の重い SQL を特定する
「今まさにシステムが遅い」というときは、V$SESSION で現在アクティブなセッションと実行中の SQL を確認します。
-- アクティブセッションと実行中 SQL の一覧
SELECT
s.sid, s.serial#,
s.username,
s.status,
s.event,
s.wait_class,
s.seconds_in_wait,
s.sql_id,
SUBSTR(q.sql_text, 1, 80) AS sql_text_short
FROM v$session s
LEFT JOIN v$sql q ON s.sql_id = q.sql_id AND s.sql_child_number = q.child_number
WHERE s.status = 'ACTIVE'
AND s.type = 'USER'
ORDER BY s.seconds_in_wait DESC;
-- アプリケーションユーザーの実行中 SQL に絞る
SELECT s.sid, s.sql_id, s.event,
s.seconds_in_wait AS wait_sec,
q.elapsed_time/1000000 AS total_elapsed_sec,
SUBSTR(q.sql_text, 1, 100) AS sql_text
FROM v$session s
JOIN v$sql q ON s.sql_id = q.sql_id
WHERE s.username = 'APP_USER'
AND s.status = 'ACTIVE';
・
User I/O: ディスク読み書き待ち(インデックス不足の可能性)・
Concurrency: ロック / ラッチ競合・
Network: DB Link / クライアント応答待ち・
CPU: CPU 割り当て待ち(CPU リソース不足)V$SQL_MONITOR で長時間 SQL の進捗を確認する
-- 5 秒以上実行中または並列実行の SQL が自動記録される
SELECT
sql_id,
status,
username,
ROUND(elapsed_time/1000000, 1) AS elapsed_sec,
ROUND(cpu_time/1000000, 1) AS cpu_sec,
buffer_gets,
disk_reads,
sql_text
FROM v$sql_monitor
WHERE status = 'EXECUTING'
ORDER BY elapsed_time DESC;
-- 特定 SQL の詳細レポート(HTML 形式)
SELECT DBMS_SQLTUNE.REPORT_SQL_MONITOR(
sql_id => 'abc123def456',
type => 'TEXT'
) AS report
FROM DUAL;
-- 実行計画の各ステップの進捗率や待機時間が確認できる
V$SQL_MONITOR と DBMS_SQLTUNE.REPORT_SQL_MONITOR はOracle Tuning Pack のライセンスに含まれます。Standard Edition では利用できません。
AWR レポートで過去の遅い SQL を特定する
AWR(Automatic Workload Repository)は、Oracle が 1 時間ごとに自動取得するパフォーマンススナップショットです。「昨日の午後 3 時頃に遅かった」というケースで、その時間帯の Top SQL を特定できます。
-- SQL*Plus で AWR レポートを生成 SQL> @$ORACLE_HOME/rdbms/admin/awrrpt.sql -- 対話形式で以下を入力: -- レポート形式: html(推奨) -- 日数: 1(直近 1 日のスナップショット一覧) -- 開始スナップショット ID: 100 -- 終了スナップショット ID: 102(例: 2 時間分) -- レポートファイル名: awr_report.html
AWR レポートの読み方: SQL セクション
| セクション名 | 内容 | 何がわかるか |
|---|---|---|
| SQL ordered by Elapsed Time | 総経過時間の Top SQL | 最も影響の大きい SQL(最初に見るべき) |
| SQL ordered by CPU Time | CPU 消費の Top SQL | CPU ボトルネックの SQL |
| SQL ordered by Gets | バッファ読み取りの Top SQL | メモリ消費の多い SQL(インデックス不足の候補) |
| SQL ordered by Reads | ディスク読み取りの Top SQL | I/O ボトルネックの SQL |
| SQL ordered by Executions | 実行回数の Top SQL | 頻繁に呼ばれる SQL(1 回は軽くても累計で影響大) |
(1) まず「SQL ordered by Elapsed Time」を見る(全体影響が最大の SQL)
(2) 次に「%Total DB Time」列を確認(DB 全体の何 % を占めるか)
(3) sql_id をメモして
V$SQL や EXPLAIN PLAN で詳細分析上位 5 件を改善するだけで全体パフォーマンスが大幅に向上することが多いです。
ASH(Active Session History)で待機イベントを分析する
-- 直近 1 時間で最も待機時間が長い SQL
SELECT sql_id,
COUNT(*) AS sample_count,
ROUND(COUNT(*) * 100.0 / SUM(COUNT(*)) OVER(), 1) AS pct,
MAX(event) AS top_event
FROM v$active_session_history
WHERE sample_time >= SYSDATE - INTERVAL '1' HOUR
AND sql_id IS NOT NULL
GROUP BY sql_id
ORDER BY sample_count DESC
FETCH FIRST 10 ROWS ONLY;
-- 特定の sql_id で何を待っていたかを分析
SELECT event, wait_class,
COUNT(*) AS samples,
ROUND(COUNT(*) * 100.0 / SUM(COUNT(*)) OVER(), 1) AS pct
FROM v$active_session_history
WHERE sql_id = 'abc123def456'
AND sample_time >= SYSDATE - INTERVAL '1' HOUR
GROUP BY event, wait_class
ORDER BY samples DESC;
-- 特定時間帯の ASH レポートを生成 SQL> @$ORACLE_HOME/rdbms/admin/ashrpt.sql -- 開始時刻と終了時刻を指定 → Top SQL / Top Events が出力される
ADDM で自動推奨を取得する
ADDM(Automatic Database Diagnostic Monitor)は、AWR スナップショットを分析し、パフォーマンス改善の推奨事項を自動生成します。「何が問題か」「どう直すべきか」を Oracle が教えてくれます。
-- ADDM レポートを生成 SQL> @$ORACLE_HOME/rdbms/admin/addmrpt.sql -- スナップショット範囲を指定すると、推奨事項付きレポートが生成される -- 結果例: -- FINDING 1: SQL ID "abc123" consumed 45% of DB Time -- RECOMMENDATION: Consider creating an index on ORDERS(CUSTOMER_ID)
Statspack(Standard Edition 向け)
AWR / ASH / ADDM は Enterprise Edition + Diagnostics Pack のライセンスが必要です。Standard Edition では代わりに Statspack を使います。Statspack は無料で利用可能で、AWR と同様のスナップショットベースの分析ができます。
-- (1) Statspack のインストール(初回のみ) -- SQL> @$ORACLE_HOME/rdbms/admin/spcreate.sql -- (2) スナップショットの手動取得 EXEC STATSPACK.SNAP; -- (3) レポートの生成 SQL> @$ORACLE_HOME/rdbms/admin/spreport.sql -- 開始/終了のスナップショット ID を指定 -- (4) 自動スナップショット(DBMS_JOB で 1 時間ごと) -- SQL> @$ORACLE_HOME/rdbms/admin/spauto.sql
| 機能 | AWR(EE + Diagnostics Pack) | Statspack(全エディション無料) |
|---|---|---|
| スナップショット | 自動(1 時間ごと) | 手動 or DBMS_JOB で自動化 |
| レポート | @awrrpt.sql | @spreport.sql |
| Top SQL | ○ | ○ |
| 待機イベント分析 | ○(ASH) | 限定的 |
| 自動推奨 | ○(ADDM) | × |
| SQL Monitor | ○ | × |
| ライセンス | 有償 | 無料 |
遅い SQL を特定した後の次のステップ
| 特定した内容 | 次のアクション |
|---|---|
| sql_id を特定した | 実行計画を確認: SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR('sql_id')) |
| フルテーブルスキャンが原因 | 適切なインデックスの作成を検討 |
| 実行計画は良いが遅い | 統計情報の更新: DBMS_STATS.GATHER_TABLE_STATS |
| ロック / 待機が原因 | V$LOCK / V$SESSION で競合セッションを特定 |
| 頻繁に実行される軽い SQL | アプリケーション側でキャッシュやバッチ化を検討 |
実行計画の確認方法の詳細は「SQL の実行計画を確認する方法」を参照してください。
実務パターン集
パターン(1): 今まさに遅い SQL をリアルタイム特定
-- (1) アクティブセッションを確認
SELECT sid, username, sql_id, event, seconds_in_wait
FROM v$session
WHERE status = 'ACTIVE' AND type = 'USER'
ORDER BY seconds_in_wait DESC;
-- (2) 特定した sql_id の SQL テキストを確認
SELECT sql_fulltext FROM v$sql WHERE sql_id = 'abc123def456';
-- (3) 実行計画を確認
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR('abc123def456'));
パターン(2): 昨日の夕方に遅かった SQL を特定(AWR)
-- (1) スナップショット ID を確認 SELECT snap_id, begin_interval_time FROM dba_hist_snapshot WHERE begin_interval_time >= TRUNC(SYSDATE-1) + 17/24 -- 昨日 17:00 AND begin_interval_time <= TRUNC(SYSDATE-1) + 19/24 -- 昨日 19:00 ORDER BY snap_id; -- (2) AWR レポートを生成 -- SQL> @$ORACLE_HOME/rdbms/admin/awrrpt.sql -- 開始 snap_id / 終了 snap_id を指定
パターン(3): 定期的に Top SQL を監視するクエリ
-- 毎朝実行: 前日の Top 10 SQL をログテーブルに記録
INSERT INTO sql_perf_log (log_date, sql_id, elapsed_sec, executions, avg_sec)
SELECT TRUNC(SYSDATE-1), sql_id,
ROUND(elapsed_time/1000000, 2),
executions,
ROUND(elapsed_time/NULLIF(executions,0)/1000000, 2)
FROM (
SELECT sql_id, elapsed_time, executions
FROM dba_hist_sqlstat s
JOIN dba_hist_snapshot sn ON s.snap_id = sn.snap_id
WHERE sn.begin_interval_time >= TRUNC(SYSDATE-1)
AND sn.begin_interval_time < TRUNC(SYSDATE)
ORDER BY elapsed_time DESC
)
WHERE ROWNUM <= 10;
COMMIT;
よくある質問
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR('sql_id', NULL, 'ALL')) で共有プール内の実行計画を確認できます。過去の実行計画は DBMS_XPLAN.DISPLAY_AWR('sql_id') で取得できます。詳細は「実行計画を確認する方法」を参照。elapsed_time - cpu_time は待機時間に相当します。この差が大きい SQL は I/O 待ち・ロック待ち・ネットワーク待ちなどで「待っている時間」が長いことを意味します。ASH で待機イベントの内訳を確認して原因を特定してください。buffer_gets は論理読み取り(メモリ上のバッファキャッシュ読み取り)の回数です。値が大きい SQL は大量のデータブロックを読んでいることを意味し、インデックスの不足やフルテーブルスキャンが原因の可能性が高いです。実行計画を確認してインデックス追加を検討してください。(1) バックグラウンドプロセス(DBWR, LGWR 等)がボトルネック →
V$SYSTEM_EVENT で確認(2) PGA / SGA のメモリ不足 →
V$PGASTAT / V$SGA で確認(3) OS レベルの問題(CPU, ディスク, ネットワーク)→
top / iostat / sar で確認(4) SQL が「瞬間的に」実行完了しているが頻度が高い → V$SQL の executions 列を確認
まとめ
遅い SQL を特定する方法をまとめます。
| 状況 | 推奨ツール | コマンド / クエリ |
|---|---|---|
| 今まさに遅い SQL を特定 | V$SESSION | SELECT sid, sql_id, event FROM v$session WHERE status=’ACTIVE’ |
| 経過時間の Top N SQL | V$SQL | SELECT … FROM v$sql ORDER BY elapsed_time DESC |
| 長時間 SQL の進捗 | V$SQL_MONITOR | SELECT … FROM v$sql_monitor WHERE status=’EXECUTING’ |
| 過去の時間帯の Top SQL | AWR レポート | @$ORACLE_HOME/rdbms/admin/awrrpt.sql |
| 待機イベントの分析 | ASH | SELECT sql_id, event FROM v$active_session_history |
| 自動推奨レポート | ADDM | @$ORACLE_HOME/rdbms/admin/addmrpt.sql |
| Standard Edition での分析 | Statspack | @$ORACLE_HOME/rdbms/admin/spreport.sql |

