Oracleのパフォーマンス問題の多くは「統計情報が古い」ことが原因です。
SQLが突然遅くなった、実行計画が変わった──そんなトラブルを解決するには、統計情報の確認と適切な収集が欠かせません。
この記事では、現場エンジニア歴20年超の筆者が、Oracleの統計情報を確認するSQLと取得(収集)する方法を、テーブル・インデックス・スキーマ単位それぞれについてまとめました。
統計情報とは
統計情報とは、テーブルのレコード件数・列の値の分布・インデックスの深さなど、オプティマイザ(実行計画エンジン)が使用するデータの特性情報です。
Oracleはこの統計情報をもとに「どのインデックスを使うか」「どの順番でテーブルを結合するか」を決定します。統計情報が実態と乖離していると、最適でない実行計画が選ばれてしまいます。
統計情報が古いと何が問題なのか
統計情報が古い=実際のデータ状態と統計の内容がずれている状態です。
たとえば、100万件だったテーブルが1,000万件に増えても、統計情報が更新されていないと、オプティマイザは「100万件テーブル」として実行計画を作ります。結果として、フルスキャンが選ばれたり、非効率なネステッドループが発生したりして、SQLが極端に遅くなります。
統計情報の自動収集と手動収集
自動収集(Oracle 10g以降)
デフォルトでは、Oracleの**自動タスク(DBMS_SCHEDULER)**が毎日夜間(22:00〜)に統計収集を実行します。ただし以下の条件があります。
- 対象は「前回収集以降にデータが10%以上変化したオブジェクト」のみ
- すべてのテーブル・インデックスが対象になるわけではない
- 大量データ処理(バッチ後など)は手動収集が必要
手動収集が必要なケース
- 大量INSERT/DELETE後
- 本番リリース前
- パフォーマンス問題発生時
- 統計が取れていないオブジェクトがある場合
【確認】統計情報が最後に取得された日付を確認するSQL
統計情報がいつ収集されたか確認するには、ALL_TABLES または DBA_TABLES を参照します。
テーブル単位の確認
sql
SELECT
AT.TABLE_NAME AS テーブル名
,AT.NUM_ROWS AS レコード件数
,AT.LAST_ANALYZED AS 最終統計取得日時
,AT.STALE_STATS AS 統計情報が古いか
FROM ALL_TABLES AT
WHERE AT.OWNER = 'オーナー名をここに入力' -- 例: 'SCOTT'
AND AT.TABLE_NAME = 'テーブル名をここに入力' -- 例: 'EMP'
;STALE_STATS列について:
YES→ 統計情報が古い(要収集)NO→ 最新の統計情報NULL→ 統計情報が一度も取得されていない
スキーマ配下の全テーブルをまとめて確認
sql
SELECT
TABLE_NAME
,NUM_ROWS
,LAST_ANALYZED
,STALE_STATS
FROM ALL_TABLES
WHERE OWNER = 'オーナー名をここに入力'
ORDER BY LAST_ANALYZED ASC NULLS FIRST -- 古いもの・未収集が先頭に
;インデックスの統計情報確認
sql
SELECT
AI.INDEX_NAME AS インデックス名
,AI.TABLE_NAME AS テーブル名
,AI.NUM_ROWS AS 行数
,AI.LAST_ANALYZED AS 最終統計取得日時
FROM ALL_INDEXES AI
WHERE AI.OWNER = 'オーナー名をここに入力'
ORDER BY AI.LAST_ANALYZED ASC NULLS FIRST
;【取得】統計情報の収集方法
統計情報の収集には DBMS_STATS パッケージを使用します。
テーブル単位で収集(基本)
sql
BEGIN
DBMS_STATS.GATHER_TABLE_STATS(
OWNNAME => 'オーナー名' -- 例: 'SCOTT'
,TABNAME => 'テーブル名' -- 例: 'EMP'
);
END;
/テーブル単位で収集(パラレル実行)
レコード件数が多いテーブルは、パラレル実行で時間を短縮できます。
sql
BEGIN
DBMS_STATS.GATHER_TABLE_STATS(
OWNNAME => 'オーナー名'
,TABNAME => 'テーブル名'
,DEGREE => 4 -- パラレル度(CPUコア数に合わせて調整)
);
END;
/注意:
DEGREEを大きくしすぎると他処理に影響します。本番環境では慎重に設定してください。
テーブルとインデックスをまとめて収集(CASCADE)
インデックスの統計もまとめて収集したい場合は CASCADE => TRUE を指定します。
sql
BEGIN
DBMS_STATS.GATHER_TABLE_STATS(
OWNNAME => 'オーナー名'
,TABNAME => 'テーブル名'
,CASCADE => TRUE -- インデックスも一緒に収集
,DEGREE => 4
);
END;
/インデックス単位で収集
sql
BEGIN
DBMS_STATS.GATHER_INDEX_STATS(
OWNNAME => 'オーナー名'
,INDNAME => 'インデックス名'
);
END;
/スキーマ全体をまとめて収集(GATHER_SCHEMA_STATS)
スキーマ配下のすべてのテーブル・インデックスをまとめて収集します。バッチ後のメンテナンス処理などに有効です。
sql
BEGIN
DBMS_STATS.GATHER_SCHEMA_STATS(
OWNNAME => 'オーナー名'
,DEGREE => 4
,CASCADE => TRUE
);
END;
/内部オブジェクト(固定オブジェクト)の統計収集
Oracleが内部で使用する固定オブジェクト(X$テーブルなど)の統計情報も定期的に収集が必要です。これを怠るとオプティマイザの精度が下がることがあります。
sql
BEGIN
DBMS_STATS.GATHER_FIXED_OBJECTS_STATS(
no_invalidate => DBMS_STATS.AUTO_INVALIDATE
);
END;
/目安: DBのメジャーパッチ適用後や、パフォーマンス問題発生時に実行してください。
統計情報を収集する際の主要パラメータ一覧
| パラメータ | 意味 | 推奨値 |
|---|---|---|
| OWNNAME | スキーマ(オーナー)名 | 必須 |
| TABNAME | テーブル名 | 必須(テーブル収集時) |
| INDNAME | インデックス名 | 必須(インデックス収集時) |
| DEGREE | パラレル度(並列数) | 2〜8(環境による) |
| CASCADE | インデックスも一緒に収集するか | TRUE推奨 |
| ESTIMATE_PERCENT | サンプリング率(%) | DBMS_STATS.AUTO_SAMPLE_SIZE推奨 |
| NO_INVALIDATE | 共有プールのカーソルを無効化するか | DBMS_STATS.AUTO_INVALIDATE推奨 |
よくある質問(FAQ)
Q. 統計情報を収集すると実行計画が変わって遅くなることはありますか?
あります。収集後に実行計画が変わって遅くなるケースは現場でもよくあります。その場合は DBMS_STATS.LOCK_TABLE_STATS で統計をロックするか、前の統計情報に戻す(RESTORE_TABLE_STATS)ことで対応できます。
Q. 自動収集と手動収集はどちらを優先すべきですか?
基本は自動収集に任せつつ、大量データ処理後やパフォーマンス問題発生時は手動で収集するのがベストです。
Q. 統計情報の収集にどれくらい時間がかかりますか?
テーブルのサイズ次第です。数百万件なら数秒〜数分、数億件になるとパラレル実行でも数十分かかることがあります。バッチウィンドウ内に収まるよう計画的に実行してください。
Q. ALL_TABLESとDBA_TABLESの違いは何ですか?
ALL_TABLES はログインユーザーがアクセス権限を持つテーブルが対象。DBA_TABLES はDB全体のテーブルが対象(DBA権限が必要)。一般ユーザーは ALL_TABLES を使用します。
まとめ
Oracleの統計情報は、SQLパフォーマンスを左右する重要な要素です。
- 統計情報の収集日時は
ALL_TABLES.LAST_ANALYZEDで確認できる - テーブル単位は
GATHER_TABLE_STATS、スキーマ全体はGATHER_SCHEMA_STATSを使用 - 大量データ処理後は必ず手動で統計を収集する
- パラレル実行(
DEGREE)で収集時間を短縮できる - 固定オブジェクトの統計(
GATHER_FIXED_OBJECTS_STATS)も定期的に実行する
定期的な統計情報の最新化がOracleパフォーマンス維持の基本です。
もっと詳しくOracleのことを知りたい方は、こちらの本をおすすめします。
セッション数やプロセス数の見方はこちらの記事で詳しく説明してます。

SQLServerの統計情報はこちらを参照










コメント