【Oracle】DBMS_STATSによる統計情報の手動収集・管理完全ガイド|GATHER_TABLE_STATS・ロック・エクスポート・ヒストグラムまで解説

【Oracle】DBMS_STATSによる統計情報の手動収集・管理完全ガイド|GATHER_TABLE_STATS・ロック・エクスポート・ヒストグラムまで解説 Oracle

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 ── テーブル単位の統計収集

最も使用頻度の高いプロシージャです。テーブル統計・列統計・インデックス統計をまとめて収集できます。

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(即時無効化で新しい統計を反映)

スキーマ単位・データベース単位の統計収集

GATHER_SCHEMA_STATS ── スキーマ内の全テーブルを収集
BEGIN
    DBMS_STATS.GATHER_SCHEMA_STATS(
        ownname          => 'HR',
        estimate_percent => DBMS_STATS.AUTO_SAMPLE_SIZE,
        cascade          => TRUE,
        degree           => 4
    );
END;
/
-- HR スキーマ内の全テーブル・インデックスの統計を一括収集
GATHER_DATABASE_STATS ── データベース全体の統計を収集
BEGIN
    DBMS_STATS.GATHER_DATABASE_STATS(
        estimate_percent => DBMS_STATS.AUTO_SAMPLE_SIZE,
        cascade          => TRUE,
        options          => 'GATHER STALE'  -- stale な統計のみ更新(全テーブルではない)
    );
END;
/
options パラメータの選択肢
GATHER:全テーブルの統計を収集(デフォルト)
GATHER STALE:stale(古い)統計のテーブルのみ更新
GATHER EMPTY:統計未収集のテーブルのみ収集
GATHER AUTO:staleまたは未収集のテーブルを自動判断して収集

統計情報の確認方法

テーブル統計の確認

DBA_TAB_STATISTICS ── テーブル統計の確認
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;

列統計の確認

DBA_TAB_COL_STATISTICS ── 列統計とヒストグラムの確認
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;

インデックス統計の確認

DBA_IND_STATISTICS ── インデックス統計の確認
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 統計のテーブルを一覧で確認する
-- 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;
テーブルの DML 量を確認する(stale 判定の根拠)
-- 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;
stale 判定の閾値を変更する
デフォルトの10%は DBMS_STATS.SET_TABLE_PREFS で変更できます。大規模テーブルでは閾値を下げることで、少量の変更でも統計が更新されるようになります。
stale 判定の閾値を5%に変更する
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以降のデフォルト
method_opt でヒストグラムを明示制御する
-- 全列に自動判断でヒストグラムを作成(デフォルト)
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_percentAUTO_SAMPLE_SIZE が推奨
  • 統計の確認DBA_TAB_STATISTICS/DBA_TAB_COL_STATISTICS/DBA_IND_STATISTICS で行数・最終収集日時・stale状態を確認
  • stale 検出:10%以上の行が変更されるとstaleと判定される。閾値は SET_TABLE_PREFS で変更可能
  • 統計ロック:実行計画を安定させたいテーブルは LOCK_TABLE_STATS で統計を固定する。ただし長期放置に注意
  • エクスポート/インポートCREATE_STAT_TABLEEXPORT_TABLE_STATSIMPORT_TABLE_STATS のパターンで統計のバックアップと復元が可能
  • ヒストグラムmethod_opt で列ごとにヒストグラムの有無を制御できる。データの偏りが大きい列では SIZE AUTO で自動作成がおすすめ
  • 遅いSQLの原因を調べる際は「【Oracle】遅いSQLを特定する方法」も参考にしてください