【Oracle】自動統計収集のスケジュールと制御方法|メンテナンスウィンドウ・統計ロック・手動収集・夜間バッチ対策まで解説

【Oracle】自動統計収集のスケジュールと制御方法|メンテナンスウィンドウ・統計ロック・手動収集・夜間バッチ対策まで解説 Oracle

Oracle の自動統計収集は、オプティマイザが正確な実行計画を立てるために不可欠な機能です。しかし、メンテナンスウィンドウで実行される自動収集が夜間バッチと競合し、パフォーマンスに影響を与えることがあります。

本記事では、自動統計収集の仕組み、メンテナンスウィンドウの確認と変更特定テーブルの統計ロック手動収集のベストプラクティスリソース制御まで解説します。

この記事でわかること
・自動統計収集の仕組みとメンテナンスウィンドウ
・スケジュールの確認と変更方法
・自動統計収集の無効化(全体 / 個別)
・特定テーブルの統計ロックで自動更新を防止
・STALE(古い)統計の検出と手動更新
・DBMS_STATS の収集オプション(DEGREE / CONCURRENT / ESTIMATE_PERCENT)
・夜間バッチとの競合回避パターン
スポンサーリンク

自動統計収集の仕組み

項目 内容
実行タイミング メンテナンスウィンドウ内(デフォルト: 平日 22:00〜翌 2:00 / 土日 6:00〜翌 2:00)
対象 STALE(10% 以上のデータ変更があった)テーブルとインデックス
実行方法 DBMS_STATS.GATHER_DATABASE_STATS_JOB_PROC(内部プロシージャ)
管理者 DBMS_AUTO_TASK_ADMIN パッケージ
デフォルト 有効(11g 以降)
SQL(自動統計収集の有効/無効を確認)
-- 自動タスクの状態を確認
SELECT client_name, status
FROM dba_autotask_client
WHERE client_name = 'auto optimizer stats collection';
-- status: ENABLED / DISABLED

メンテナンスウィンドウの確認と変更

SQL(現在のメンテナンスウィンドウを確認)
-- メンテナンスウィンドウの一覧
SELECT window_name, repeat_interval, duration, enabled
FROM dba_scheduler_windows
WHERE window_name LIKE '%WINDOW%'
ORDER BY window_name;

-- 実行履歴(直近 7 日)
SELECT window_name, window_start_time, window_duration, window_end_time
FROM dba_autotask_window_history
WHERE window_start_time >= SYSDATE - 7
ORDER BY window_start_time DESC;
ウィンドウ名 デフォルトスケジュール 期間
MONDAY_WINDOW 月曜 22:00 4 時間
TUESDAY_WINDOW 火曜 22:00 4 時間
WEDNESDAY_WINDOW 水曜 22:00 4 時間
THURSDAY_WINDOW 木曜 22:00 4 時間
FRIDAY_WINDOW 金曜 22:00 4 時間
SATURDAY_WINDOW 土曜 06:00 20 時間
SUNDAY_WINDOW 日曜 06:00 20 時間
SQL(メンテナンスウィンドウの時間を変更)
-- 月曜のウィンドウを 1:00 開始に変更(夜間バッチの後に実行)
BEGIN
    DBMS_SCHEDULER.SET_ATTRIBUTE(
        name      => 'MONDAY_WINDOW',
        attribute => 'repeat_interval',
        value     => 'FREQ=WEEKLY; BYDAY=MON; BYHOUR=1; BYMINUTE=0; BYSECOND=0'
    );
END;
/
SQL(ウィンドウの期間を変更)
-- 月曜のウィンドウを 2 時間に短縮
BEGIN
    DBMS_SCHEDULER.SET_ATTRIBUTE(
        name      => 'MONDAY_WINDOW',
        attribute => 'duration',
        value     => INTERVAL '2' HOUR
    );
END;
/
夜間バッチとの競合を避けるコツ
・バッチが 22:00〜0:00 に実行される場合: ウィンドウの開始を 1:00 以降に変更
・バッチが 0:00〜4:00 に実行される場合: ウィンドウの開始を 5:00 以降に変更
・バッチの終了後にウィンドウが始まるように調整するのが基本です

自動統計収集の無効化

全体を無効化

SQL(自動統計収集を完全に無効化)
-- 自動統計収集を無効化
BEGIN
    DBMS_AUTO_TASK_ADMIN.DISABLE(
        client_name => 'auto optimizer stats collection',
        operation   => NULL,
        window_name => NULL
    );
END;
/

-- 再度有効化する場合
BEGIN
    DBMS_AUTO_TASK_ADMIN.ENABLE(
        client_name => 'auto optimizer stats collection',
        operation   => NULL,
        window_name => NULL
    );
END;
/

特定の曜日だけ無効化

SQL(月曜だけ無効化: バッチが月曜夜に走る場合)
-- 月曜のウィンドウだけ無効化
BEGIN
    DBMS_SCHEDULER.DISABLE('MONDAY_WINDOW');
END;
/

-- 再度有効化
BEGIN
    DBMS_SCHEDULER.ENABLE('MONDAY_WINDOW');
END;
/
自動統計収集を完全に無効化するリスク
自動統計収集を無効化すると、テーブルの統計情報が古いまま放置されます。オプティマイザが不正確な実行計画を選び、SELECT のパフォーマンスが著しく低下する可能性があります。無効化する場合は手動での定期収集を必ずスケジュールしてください。

特定テーブルの統計をロックする

自動統計収集自体は有効のまま、特定テーブルだけ自動更新されないようにロックできます。大量データ投入中に統計が中途半端に更新されるのを防ぐ場合などに使います。

SQL(統計のロックとアンロック)
-- 特定テーブルの統計をロック(自動収集対象から除外)
EXEC DBMS_STATS.LOCK_TABLE_STATS('HR', 'EMPLOYEES');

-- スキーマ全体の統計をロック
EXEC DBMS_STATS.LOCK_SCHEMA_STATS('HR');

-- ロックを解除
EXEC DBMS_STATS.UNLOCK_TABLE_STATS('HR', 'EMPLOYEES');
EXEC DBMS_STATS.UNLOCK_SCHEMA_STATS('HR');

-- ロック状態の確認
SELECT table_name, stattype_locked FROM dba_tab_statistics
WHERE owner = 'HR' AND stattype_locked IS NOT NULL;
統計ロックの活用場面
・バッチ投入中に統計が変わるのを防ぐ(投入完了後にアンロック + 手動収集)
・パーティションテーブルで特定パーティションの統計を固定
・テスト用に特定の統計状態を保持したい場合

STALE(古い)統計の検出

SQL(STALE 統計の確認)
-- STALE 統計のテーブルを検出
SELECT owner, table_name, last_analyzed, stale_stats, num_rows
FROM dba_tab_statistics
WHERE owner NOT IN ('SYS','SYSTEM','AUDSYS','XDB')
  AND stale_stats = 'YES'
ORDER BY num_rows DESC;

-- stale_stats = YES: 最後の統計収集以降に 10% 以上のデータ変更
-- → 手動で GATHER すべき候補
SQL(統計が古いテーブルの一覧)
-- 30 日以上統計が更新されていないテーブル
SELECT owner, table_name, last_analyzed, num_rows
FROM dba_tables
WHERE owner = 'HR'
  AND (last_analyzed IS NULL OR last_analyzed < SYSDATE - 30)
ORDER BY last_analyzed;

手動統計収集のベストプラクティス

SQL(手動統計収集の各パターン)
-- テーブル単位
EXEC DBMS_STATS.GATHER_TABLE_STATS('HR', 'EMPLOYEES');

-- スキーマ全体
EXEC DBMS_STATS.GATHER_SCHEMA_STATS('HR');

-- STALE のテーブルだけ収集
EXEC DBMS_STATS.GATHER_SCHEMA_STATS('HR', OPTIONS => 'GATHER STALE');

-- 空のテーブルだけ収集
EXEC DBMS_STATS.GATHER_SCHEMA_STATS('HR', OPTIONS => 'GATHER EMPTY');

-- データベース全体の STALE テーブルを収集
EXEC DBMS_STATS.GATHER_DATABASE_STATS(OPTIONS => 'GATHER STALE');

収集オプション

オプション 意味 推奨値
ESTIMATE_PERCENT サンプリング率 DBMS_STATS.AUTO_SAMPLE_SIZE(デフォルト: Oracle が自動判断)
METHOD_OPT 列統計の収集方法 ‘FOR ALL COLUMNS SIZE AUTO’(デフォルト: Oracle が自動判断)
DEGREE 並列度 DBMS_STATS.AUTO_DEGREE(Oracle が自動判断)または具体的な数値
CASCADE インデックス統計も同時に収集 DBMS_STATS.AUTO_CASCADE(デフォルト)
NO_INVALIDATE 既存カーソルの無効化を遅延 DBMS_STATS.AUTO_INVALIDATE(デフォルト: 段階的に無効化)
SQL(オプションを明示指定した収集)
-- 並列度 4、サンプリング率は自動、カスケードあり
BEGIN
    DBMS_STATS.GATHER_TABLE_STATS(
        ownname          => 'HR',
        tabname          => 'ORDERS',
        estimate_percent => DBMS_STATS.AUTO_SAMPLE_SIZE,
        method_opt       => 'FOR ALL COLUMNS SIZE AUTO',
        degree           => 4,
        cascade          => TRUE
    );
END;
/

CONCURRENT 統計収集(並列テーブル収集)

SQL(CONCURRENT の有効化)
-- CONCURRENT: 複数テーブルの統計を同時に収集(12c+)
EXEC DBMS_STATS.SET_GLOBAL_PREFS('CONCURRENT', 'TRUE');

-- 確認
SELECT DBMS_STATS.GET_PREFS('CONCURRENT') FROM DUAL;

-- CONCURRENT + GATHER STALE でスキーマ全体を高速収集
EXEC DBMS_STATS.GATHER_SCHEMA_STATS('HR', OPTIONS => 'GATHER STALE');
-- 複数テーブルが同時に収集される
CONCURRENT は大規模スキーマで効果的
テーブルが多い(数百〜数千)スキーマでは、CONCURRENT を有効にすると統計収集の総時間を大幅に短縮できます。ただし CPU / I/O リソースを大量に消費するため、業務時間外に実行してください。

夜間バッチとの競合回避パターン

パターン 方法 適するケース
ウィンドウの時間をずらす DBMS_SCHEDULER.SET_ATTRIBUTE でバッチ終了後に変更 最もシンプル。多くの環境で有効
バッチ日だけ無効化 DBMS_SCHEDULER.DISABLE(曜日_WINDOW) バッチが特定曜日のみの場合
バッチ完了後に手動収集 バッチスクリプトの末尾に GATHER_STALE を追加 大量データ投入後に統計を即座に更新
統計ロック + 手動収集 バッチ中は LOCK_TABLE_STATS、完了後に UNLOCK + GATHER バッチ中の中途半端な統計更新を防止
自動収集を無効化 + 独自スケジュール DBMS_AUTO_TASK_ADMIN.DISABLE + DBMS_SCHEDULER で独自ジョブ 完全にカスタマイズしたい場合
SQL(バッチスクリプトに手動収集を組み込む)
#!/bin/bash
# nightly_batch.sh

# (1) バッチ投入前: 統計をロック
sqlplus hr/password <<EOF
EXEC DBMS_STATS.LOCK_TABLE_STATS('HR', 'ORDERS');
EOF

# (2) バッチ投入
sqlldr hr/password control=orders.ctl ...

# (3) バッチ完了後: アンロック + 統計収集
sqlplus hr/password <<EOF
EXEC DBMS_STATS.UNLOCK_TABLE_STATS('HR', 'ORDERS');
EXEC DBMS_STATS.GATHER_TABLE_STATS('HR', 'ORDERS', DEGREE => 4);
EOF

PENDING 統計(テスト収集)

統計を収集しても即座に適用せず、テスト後に反映する機能です。大テーブルの統計変更で実行計画が急変するリスクを軽減できます。

SQL(PENDING 統計の使い方)
-- PENDING モードを有効化
EXEC DBMS_STATS.SET_TABLE_PREFS('HR', 'ORDERS', 'PUBLISH', 'FALSE');

-- 統計を収集(まだ反映されない)
EXEC DBMS_STATS.GATHER_TABLE_STATS('HR', 'ORDERS');

-- PENDING 統計を使って実行計画をテスト
ALTER SESSION SET OPTIMIZER_USE_PENDING_STATISTICS = TRUE;
-- テスト SQL を実行して計画を確認

-- 問題なければ PENDING 統計を公開(反映)
EXEC DBMS_STATS.PUBLISH_PENDING_STATS('HR', 'ORDERS');

-- 問題があればPENDING 統計を削除(元に戻す)
EXEC DBMS_STATS.DELETE_PENDING_STATS('HR', 'ORDERS');

-- PUBLISH モードを元に戻す
EXEC DBMS_STATS.SET_TABLE_PREFS('HR', 'ORDERS', 'PUBLISH', 'TRUE');

統計情報の復元

SQL(統計を以前の状態に戻す)
-- 統計変更前の状態に戻す(デフォルトで 31 日分保持)
BEGIN
    DBMS_STATS.RESTORE_TABLE_STATS(
        ownname    => 'HR',
        tabname    => 'ORDERS',
        as_of_timestamp => SYSDATE - 1  -- 1 日前の統計に戻す
    );
END;
/

-- 統計の履歴を確認
SELECT table_name, stats_update_time
FROM dba_tab_stats_history
WHERE owner = 'HR' AND table_name = 'ORDERS'
ORDER BY stats_update_time DESC;

-- 保持期間の確認
SELECT DBMS_STATS.GET_STATS_HISTORY_RETENTION FROM DUAL;
-- デフォルト: 31 日
統計収集後にパフォーマンスが悪化したら復元する
統計を更新した直後に実行計画が変わってパフォーマンスが劣化した場合、RESTORE_TABLE_STATS で以前の統計に戻せます。31 日分の統計履歴がデフォルトで保持されるため、安心して統計を更新できます。

よくある質問

Q自動統計収集はいつ実行されますか?
Aデフォルトではメンテナンスウィンドウ内(平日 22:00〜翌 2:00、土日 6:00〜翌 2:00)に実行されます。STALE(10% 以上データが変更された)テーブルのみが対象です。DBA_AUTOTASK_WINDOW_HISTORY で実行履歴を確認できます。
Q自動統計収集を無効化しても大丈夫ですか?
A無効化するとテーブルの統計が古いまま放置され、実行計画が不正確になってパフォーマンスが劣化します。無効化する場合は必ず手動での定期収集(DBMS_STATS.GATHER_SCHEMA_STATS等)をスケジュールしてください。
QSTALE 統計とは何ですか?
Aテーブルのデータが最後の統計収集以降に 10% 以上変更された場合、その統計は STALE(古い)とマークされます。自動統計収集は STALE テーブルを優先的に収集します。DBA_TAB_STATISTICS.STALE_STATS で確認できます。
Q統計収集中にテーブルへの DML はブロックされますか?
ADBMS_STATS はテーブルの共有ロックを取得するため、DML(INSERT/UPDATE/DELETE)はブロックされません。ただし統計収集自体が I/O を消費するため、パフォーマンスへの間接的な影響はあります。
Q統計を収集したら実行計画が変わって遅くなりました
ADBMS_STATS.RESTORE_TABLE_STATS で以前の統計に復元してください。また、PENDING 統計機能を使えば、事前にテストしてから反映できるため、計画変更のリスクを軽減できます。
Qバッチ投入後は統計を更新すべきですか?
Aはい。大量の INSERT / DELETE / UPDATE を行った後は統計が実態と乖離するため、バッチ完了後に手動で GATHER することを推奨します。バッチスクリプトの末尾に GATHER_TABLE_STATS を組み込むのが最も確実です。

まとめ

自動統計収集の制御の要点をまとめます。

やりたいこと 方法
自動収集の状態を確認 SELECT status FROM dba_autotask_client WHERE client_name = ‘auto optimizer stats collection’
メンテナンスウィンドウの時間を変更 DBMS_SCHEDULER.SET_ATTRIBUTE(‘MONDAY_WINDOW’, ‘repeat_interval’, …)
自動収集を完全に無効化 DBMS_AUTO_TASK_ADMIN.DISABLE(client_name => ‘auto optimizer stats collection’)
特定テーブルの統計をロック DBMS_STATS.LOCK_TABLE_STATS(‘OWNER’, ‘TABLE’)
STALE テーブルだけ手動収集 DBMS_STATS.GATHER_SCHEMA_STATS(‘OWNER’, OPTIONS => ‘GATHER STALE’)
統計を以前の状態に復元 DBMS_STATS.RESTORE_TABLE_STATS(‘OWNER’, ‘TABLE’, as_of_timestamp => SYSDATE-1)
PENDING 統計でテストしてから反映 SET_TABLE_PREFS(‘PUBLISH’,’FALSE’) → GATHER → テスト → PUBLISH_PENDING_STATS

実行計画の確認方法は「SQL の実行計画を確認する方法」、遅い SQL の特定は「遅い SQL を特定する方法」も併せて参照してください。