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 以降) |
-- 自動タスクの状態を確認 SELECT client_name, status FROM dba_autotask_client WHERE client_name = 'auto optimizer stats collection'; -- status: ENABLED / DISABLED
メンテナンスウィンドウの確認と変更
-- メンテナンスウィンドウの一覧 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 時間 |
-- 月曜のウィンドウを 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;
/
-- 月曜のウィンドウを 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 以降に変更
・バッチの終了後にウィンドウが始まるように調整するのが基本です
自動統計収集の無効化
全体を無効化
-- 自動統計収集を無効化
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;
/
特定の曜日だけ無効化
-- 月曜のウィンドウだけ無効化
BEGIN
DBMS_SCHEDULER.DISABLE('MONDAY_WINDOW');
END;
/
-- 再度有効化
BEGIN
DBMS_SCHEDULER.ENABLE('MONDAY_WINDOW');
END;
/
自動統計収集を無効化すると、テーブルの統計情報が古いまま放置されます。オプティマイザが不正確な実行計画を選び、SELECT のパフォーマンスが著しく低下する可能性があります。無効化する場合は手動での定期収集を必ずスケジュールしてください。
特定テーブルの統計をロックする
自動統計収集自体は有効のまま、特定テーブルだけ自動更新されないようにロックできます。大量データ投入中に統計が中途半端に更新されるのを防ぐ場合などに使います。
-- 特定テーブルの統計をロック(自動収集対象から除外)
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(古い)統計の検出
-- 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 すべき候補
-- 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;
手動統計収集のベストプラクティス
-- テーブル単位
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(デフォルト: 段階的に無効化) |
-- 並列度 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 統計収集(並列テーブル収集)
-- 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 を有効にすると統計収集の総時間を大幅に短縮できます。ただし 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 で独自ジョブ | 完全にカスタマイズしたい場合 |
#!/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 統計(テスト収集)
統計を収集しても即座に適用せず、テスト後に反映する機能です。大テーブルの統計変更で実行計画が急変するリスクを軽減できます。
-- 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');
統計情報の復元
-- 統計変更前の状態に戻す(デフォルトで 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 日分の統計履歴がデフォルトで保持されるため、安心して統計を更新できます。
よくある質問
まとめ
自動統計収集の制御の要点をまとめます。
| やりたいこと | 方法 |
|---|---|
| 自動収集の状態を確認 | 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 を特定する方法」も併せて参照してください。

