OracleのSQLオプティマイザは統計情報をもとに実行計画を決定します。統計情報が古かったり存在しなかったりすると、意図しないフルテーブルスキャンや非効率なJOIN方式が選ばれ、SQLが極端に遅くなることがあります。大量データのロード後やテーブル構造の変更後にはDBMS_STATSパッケージを使って手動で統計情報を収集するのが安全です。本記事ではGATHER_TABLE_STATSの主要パラメータから、統計の確認・ロック・エクスポート/インポート・ヒストグラム管理まで体系的に解説します。
- DBMS_STATS.GATHER_TABLE_STATS の基本構文と主要パラメータ
- スキーマ単位・データベース単位での統計収集方法
- DBA_TAB_STATISTICS / DBA_TAB_COL_STATISTICS での統計確認SQL
- stale(古い)統計の検出方法
- 統計情報のロック/アンロックの使いどころ
- 統計のエクスポート/インポートによるバックアップ・移行
- ヒストグラムの種類と制御方法
- 統計の削除とリセット
なぜ統計情報の手動収集が必要なのか
Oracleにはメンテナンスウィンドウで自動的に統計を収集する仕組みがありますが、次のような場面では手動収集が必要です。
- 大量データのロード直後:バッチ処理で数百万行をINSERTした直後は、統計が実データと大きく乖離する
- TRUNCATE + 再ロード:TRUNCATEは統計情報をリセットしないため、行数0件のテーブルに対する古い統計が残る
- パーティション操作の後:パーティションの追加・交換後にはパーティション統計の更新が必要
- 夜間バッチの後・業務開始前:自動収集を待たずにすぐ反映したい場合
- 実行計画を固定した後の検証:特定のテーブルだけ統計を更新して計画の変化を確認する場合
GATHER_TABLE_STATS ── テーブル単位の統計収集
最も使用頻度の高いプロシージャです。テーブル統計・列統計・インデックス統計をまとめて収集できます。
BEGIN
DBMS_STATS.GATHER_TABLE_STATS(
ownname => 'HR', -- スキーマ名(NULLで現在のスキーマ)
tabname => 'EMPLOYEES', -- テーブル名
estimate_percent => DBMS_STATS.AUTO_SAMPLE_SIZE, -- サンプリング率(自動推奨)
method_opt => 'FOR ALL COLUMNS SIZE AUTO', -- ヒストグラム制御
cascade => TRUE, -- インデックス統計も同時に収集
degree => 4, -- 並列度(NULLでデフォルト)
no_invalidate => FALSE -- 依存カーソルを即座に無効化
);
END;
/
主要パラメータの解説
| パラメータ | 説明 | 推奨値 |
|---|---|---|
ownname |
統計を収集するスキーマ名 | 対象スキーマ名(NULLで現在のスキーマ) |
tabname |
テーブル名 | 対象テーブル名 |
estimate_percent |
サンプリング率(0.01〜100、AUTO_SAMPLE_SIZE) | DBMS_STATS.AUTO_SAMPLE_SIZE(Oracle 11g+で最適な率を自動決定) |
method_opt |
列統計・ヒストグラムの収集方法 | FOR ALL COLUMNS SIZE AUTO(自動でヒストグラムを判断) |
cascade |
インデックス統計も同時に収集するか | TRUE または DBMS_STATS.AUTO_CASCADE |
degree |
並列処理の並列度 | NULL(テーブルの並列度を使用)または明示的な値 |
granularity |
パーティションテーブルでの収集粒度 | AUTO(パーティション・サブパーティション・グローバルを自動判断) |
no_invalidate |
既存のSQLカーソルを無効化するか | FALSE(即時無効化で新しい統計を反映) |
スキーマ単位・データベース単位の統計収集
BEGIN
DBMS_STATS.GATHER_SCHEMA_STATS(
ownname => 'HR',
estimate_percent => DBMS_STATS.AUTO_SAMPLE_SIZE,
cascade => TRUE,
degree => 4
);
END;
/
-- HR スキーマ内の全テーブル・インデックスの統計を一括収集
BEGIN
DBMS_STATS.GATHER_DATABASE_STATS(
estimate_percent => DBMS_STATS.AUTO_SAMPLE_SIZE,
cascade => TRUE,
options => 'GATHER STALE' -- stale な統計のみ更新(全テーブルではない)
);
END;
/
GATHER:全テーブルの統計を収集(デフォルト)GATHER STALE:stale(古い)統計のテーブルのみ更新GATHER EMPTY:統計未収集のテーブルのみ収集GATHER AUTO:staleまたは未収集のテーブルを自動判断して収集
統計情報の確認方法
テーブル統計の確認
SELECT
table_name,
num_rows, -- 推定行数
blocks, -- 使用ブロック数
avg_row_len, -- 平均行長(バイト)
sample_size, -- サンプリングした行数
last_analyzed, -- 最終収集日時
stale_stats -- YES=古い統計, NO=最新
FROM dba_tab_statistics
WHERE owner = 'HR'
ORDER BY last_analyzed DESC NULLS LAST;
列統計の確認
SELECT
column_name,
num_distinct, -- カーディナリティ(一意値の数)
num_nulls, -- NULL値の数
density, -- 密度(1/num_distinct に近い値)
low_value, -- 最小値(RAW形式)
high_value, -- 最大値(RAW形式)
histogram, -- ヒストグラムの種類(NONE/FREQUENCY/HEIGHT BALANCED/HYBRID/TOP-FREQUENCY)
num_buckets, -- ヒストグラムのバケット数
last_analyzed
FROM dba_tab_col_statistics
WHERE owner = 'HR'
AND table_name = 'EMPLOYEES'
ORDER BY column_name;
インデックス統計の確認
SELECT
index_name,
blevel, -- B*Tree のレベル数
leaf_blocks, -- リーフブロック数
distinct_keys, -- 一意キーの数
avg_leaf_blocks_per_key,
avg_data_blocks_per_key,
clustering_factor, -- クラスタリングファクタ(小さいほどインデックススキャンに有利)
last_analyzed,
stale_stats
FROM dba_ind_statistics
WHERE owner = 'HR'
AND table_name = 'EMPLOYEES'
ORDER BY index_name;
stale(古い)統計の検出
テーブルの10%以上の行が変更されると、Oracleはその統計を「stale」と判定します。staleなテーブルを一覧で確認できます。
-- stale_stats = YES のテーブルが統計更新の対象
SELECT
owner,
table_name,
num_rows,
last_analyzed,
stale_stats
FROM dba_tab_statistics
WHERE stale_stats = 'YES'
AND owner = 'HR'
ORDER BY table_name;
-- DBA_TAB_MODIFICATIONS で最終統計収集以降の変更量を確認
SELECT
table_name,
inserts,
updates,
deletes,
timestamp AS last_flush_time
FROM dba_tab_modifications
WHERE table_owner = 'HR'
ORDER BY (inserts + updates + deletes) DESC;
デフォルトの10%は
DBMS_STATS.SET_TABLE_PREFS で変更できます。大規模テーブルでは閾値を下げることで、少量の変更でも統計が更新されるようになります。
BEGIN
DBMS_STATS.SET_TABLE_PREFS(
ownname => 'HR',
tabname => 'EMPLOYEES',
pname => 'STALE_PERCENT',
pvalue => '5' -- デフォルト10%を5%に変更
);
END;
/
統計情報のロック/アンロック
自動統計収集や手動収集によって統計が意図せず変わると、安定していた実行計画が突然変化してパフォーマンスが劣化することがあります。統計を「ロック」すると、ロック解除するまで統計は更新されなくなります。
-- 統計をロックする(自動・手動問わず更新されなくなる)
BEGIN
DBMS_STATS.LOCK_TABLE_STATS('HR', 'EMPLOYEES');
END;
/
-- ロック状態を確認する
SELECT table_name, stattype_locked
FROM dba_tab_statistics
WHERE owner = 'HR'
AND table_name = 'EMPLOYEES';
-- stattype_locked が 'ALL' ならロック済み、NULL なら未ロック
-- アンロックして統計の更新を再開可能にする
BEGIN
DBMS_STATS.UNLOCK_TABLE_STATS('HR', 'EMPLOYEES');
END;
/
-- スキーマ内の全テーブルの統計をロックする
BEGIN
DBMS_STATS.LOCK_SCHEMA_STATS('HR');
END;
/
-- スキーマ内の全テーブルの統計をアンロックする
BEGIN
DBMS_STATS.UNLOCK_SCHEMA_STATS('HR');
END;
/
ロック中に
GATHER_TABLE_STATS を実行すると ORA-20005 エラーが発生します。また、自動統計収集もそのテーブルをスキップします。ロックを長期間放置すると統計が古くなり続けるため、定期的な見直しが必要です。
統計のエクスポート/インポート
統計情報をテーブルに保存(エクスポート)し、後から復元(インポート)できます。統計変更前のバックアップ、検証環境への統計移行、統計のバージョン管理に使います。
統計保存テーブルの作成
-- DBMS_STATS が使用する統計保存テーブルを作成する
BEGIN
DBMS_STATS.CREATE_STAT_TABLE(
ownname => 'HR',
stattab => 'STATS_BACKUP' -- 任意のテーブル名
);
END;
/
統計のエクスポート(バックアップ)
BEGIN
DBMS_STATS.EXPORT_TABLE_STATS(
ownname => 'HR',
tabname => 'EMPLOYEES',
stattab => 'STATS_BACKUP',
statid => 'BEFORE_20260329' -- 識別用のID(任意の文字列)
);
END;
/
-- スキーマ単位のエクスポートも可能
-- DBMS_STATS.EXPORT_SCHEMA_STATS('HR', 'STATS_BACKUP', 'BEFORE_20260329');
統計のインポート(復元)
-- 保存しておいた統計をテーブルに戻す
BEGIN
DBMS_STATS.IMPORT_TABLE_STATS(
ownname => 'HR',
tabname => 'EMPLOYEES',
stattab => 'STATS_BACKUP',
statid => 'BEFORE_20260329',
no_invalidate => FALSE -- カーソルを即座に無効化して新しい統計を反映
);
END;
/
大量データのロード前に統計をエクスポートしておき、ロード後に
GATHER_TABLE_STATS を実行します。万一ロード後の実行計画が劣化した場合、IMPORT_TABLE_STATS で即座に元の統計に戻せます。
ヒストグラムの種類と制御
ヒストグラムは列内のデータ分布をオプティマイザに伝える情報です。データの偏り(スキュー)が大きい列では、ヒストグラムの有無で実行計画が大きく変わります。
| ヒストグラム種別 | Oracle バージョン | 特徴 |
|---|---|---|
| NONE | 全バージョン | ヒストグラムなし。均一分布を仮定する |
| FREQUENCY | 全バージョン | 値ごとの出現頻度を保持。カーディナリティが低い列(値の種類 <= バケット数)に適用 |
| HEIGHT BALANCED | 〜11g | 等高ヒストグラム。バケットに均等な行数を割り当てる |
| TOP-FREQUENCY | 12c〜 | 出現頻度が高い上位N値のみ記録。高カーディナリティ列の人気値に有効 |
| HYBRID | 12c〜 | FREQUENCYとHEIGHT BALANCEDの混合型。12c以降のデフォルト |
-- 全列に自動判断でヒストグラムを作成(デフォルト)
BEGIN
DBMS_STATS.GATHER_TABLE_STATS('HR', 'EMPLOYEES',
method_opt => 'FOR ALL COLUMNS SIZE AUTO');
END;
/
-- 全列でヒストグラムなし(均一分布を前提にする)
BEGIN
DBMS_STATS.GATHER_TABLE_STATS('HR', 'EMPLOYEES',
method_opt => 'FOR ALL COLUMNS SIZE 1');
END;
/
-- 特定の列だけヒストグラムを作成する
BEGIN
DBMS_STATS.GATHER_TABLE_STATS('HR', 'EMPLOYEES',
method_opt => 'FOR ALL COLUMNS SIZE 1 FOR COLUMNS SIZE 254 department_id job_id');
END;
/
-- department_id と job_id にだけ最大254バケットのヒストグラムを作成
統計の削除とリセット
テスト目的で統計を削除したい場合や、動的サンプリングにフォールバックさせたい場合に使います。
-- テーブル統計を削除する(オプティマイザは動的サンプリングにフォールバック)
BEGIN
DBMS_STATS.DELETE_TABLE_STATS('HR', 'EMPLOYEES');
END;
/
-- スキーマ単位で削除
BEGIN
DBMS_STATS.DELETE_SCHEMA_STATS('HR');
END;
/
-- 列統計だけ削除
BEGIN
DBMS_STATS.DELETE_COLUMN_STATS('HR', 'EMPLOYEES', 'SALARY');
END;
/
実務パターン:大量データロード後の統計更新手順
本番環境でデータロードを行った後の推奨手順です。
-- ステップ 1: 現在の統計をバックアップする
BEGIN
DBMS_STATS.EXPORT_TABLE_STATS('HR', 'EMPLOYEES', 'STATS_BACKUP', 'PRE_LOAD');
END;
/
-- ステップ 2: データロード実行
-- (INSERT, SQL*Loader, Data Pump 等)
-- ステップ 3: 統計を再収集する
BEGIN
DBMS_STATS.GATHER_TABLE_STATS(
ownname => 'HR',
tabname => 'EMPLOYEES',
estimate_percent => DBMS_STATS.AUTO_SAMPLE_SIZE,
method_opt => 'FOR ALL COLUMNS SIZE AUTO',
cascade => TRUE,
degree => 4,
no_invalidate => FALSE
);
END;
/
-- ステップ 4: 収集結果を確認する
SELECT table_name, num_rows, blocks, last_analyzed, stale_stats
FROM dba_tab_statistics
WHERE owner = 'HR' AND table_name = 'EMPLOYEES';
-- ステップ 5: (万一実行計画が劣化した場合)統計を元に戻す
-- BEGIN
-- DBMS_STATS.IMPORT_TABLE_STATS('HR', 'EMPLOYEES', 'STATS_BACKUP', 'PRE_LOAD');
-- END;
-- /
DBMS_STATS 主要プロシージャまとめ
| プロシージャ | 対象 | 用途 |
|---|---|---|
GATHER_TABLE_STATS |
テーブル | テーブル・列・インデックスの統計を収集する |
GATHER_INDEX_STATS |
インデックス | インデックス統計だけを収集する |
GATHER_SCHEMA_STATS |
スキーマ | スキーマ内の全テーブルの統計を収集する |
GATHER_DATABASE_STATS |
データベース | 全スキーマの統計を収集する |
LOCK_TABLE_STATS |
テーブル | 統計を変更不可にする |
UNLOCK_TABLE_STATS |
テーブル | 統計のロックを解除する |
EXPORT_TABLE_STATS |
テーブル | 統計を保存テーブルにエクスポートする |
IMPORT_TABLE_STATS |
テーブル | 保存テーブルから統計をインポートする |
DELETE_TABLE_STATS |
テーブル | テーブル統計を削除する |
CREATE_STAT_TABLE |
テーブル | 統計保存用のテーブルを作成する |
SET_TABLE_PREFS |
テーブル | 統計収集のプリファレンス(閾値等)を設定する |
まとめ
Oracleのオプティマイザが最適な実行計画を選ぶためには、テーブル・列・インデックスの統計情報が正確であることが不可欠です。DBMS_STATSパッケージを使いこなすことで、パフォーマンスの安定性と可視性が大幅に向上します。
- GATHER_TABLE_STATS:大量データロード後や業務開始前にテーブル単位で統計を手動収集する。
estimate_percentはAUTO_SAMPLE_SIZEが推奨 - 統計の確認:
DBA_TAB_STATISTICS/DBA_TAB_COL_STATISTICS/DBA_IND_STATISTICSで行数・最終収集日時・stale状態を確認 - stale 検出:10%以上の行が変更されるとstaleと判定される。閾値は
SET_TABLE_PREFSで変更可能 - 統計ロック:実行計画を安定させたいテーブルは
LOCK_TABLE_STATSで統計を固定する。ただし長期放置に注意 - エクスポート/インポート:
CREATE_STAT_TABLE→EXPORT_TABLE_STATS→IMPORT_TABLE_STATSのパターンで統計のバックアップと復元が可能 - ヒストグラム:
method_optで列ごとにヒストグラムの有無を制御できる。データの偏りが大きい列ではSIZE AUTOで自動作成がおすすめ - 遅いSQLの原因を調べる際は「【Oracle】遅いSQLを特定する方法」も参考にしてください

