Oracle のオプティマイザが正しい実行計画を選ぶためには、テーブルの各列にどのような値がどのくらい分布しているかという情報が必要です。この分布情報を表すのがヒストグラム(Histogram)です。
ヒストグラムがない場合、オプティマイザは「値は均等に分布している」と仮定してカーディナリティ(WHERE 条件に一致する推定行数)を計算します。データに大きな偏りがある列でこの仮定が外れると、インデックスを使うべき場面でフルスキャンを選ぶ、あるいはその逆といった実行計画の崩れが発生します。
この記事でわかること
- ヒストグラムが必要な理由(データの偏りとカーディナリティ推定)
- Frequency / Height-Balanced / Top-Frequency / Hybrid ヒストグラムの違い
- USER_HISTOGRAMS / USER_TAB_COL_STATISTICS でヒストグラムを確認する方法
- METHOD_OPT でヒストグラムの収集対象と方法を制御する方法
- バインドピーキングとヒストグラムの関係・ACS(適応カーソル共有)との連携
ヒストグラムの種類(Oracle のバージョン別)
| 種類 | バケット数 | 適用条件 | 特徴 |
|---|---|---|---|
| Frequency | 値の種類数(最大 2048) | 異なる値の数(NDV)≤ 2048 / HIST_NUM_BUCKETS | 各値の正確な頻度を記録。最も精度が高い |
| Height-Balanced | 最大 254 | NDV が多い場合(12c 以前のデフォルト) | 等頻度バケット。12c 以降は非推奨(代わりに Hybrid を使う) |
| Top-Frequency(12c 以降) | 最大 2048 | NDV > HIST_NUM_BUCKETS かつ上位 N 値で大半を占める | 頻出値のみを Frequency 形式で記録。希少値は残余として扱う |
| Hybrid(12c 以降) | 最大 2048 | NDV > HIST_NUM_BUCKETS で Top-Frequency が適さない場合 | Height-Balanced と Frequency のハイブリッド。12c のデフォルト |
ヒストグラムを確認する
USER_TAB_COL_STATISTICS と USER_HISTOGRAMS でヒストグラムを確認する
-- 列の統計とヒストグラムの有無を確認する
SELECT
column_name,
num_distinct AS ndv, -- 異なる値の数
num_nulls, -- NULL の数
density, -- オプティマイザが使う密度(1/NDV に近い値)
histogram, -- NONE / FREQUENCY / HEIGHT BALANCED / TOP-FREQUENCY / HYBRID
num_buckets, -- ヒストグラムのバケット数
last_analyzed
FROM USER_TAB_COL_STATISTICS
WHERE table_name = 'ORDERS'
ORDER BY column_name;
-- ヒストグラムの詳細(各バケットのエンドポイント値と頻度)
SELECT
endpoint_number, -- バケット番号(Frequency の場合は累積頻度)
endpoint_value, -- バケットの上限値(数値の場合)
endpoint_actual_value, -- 実際の値(文字列の場合)
endpoint_repeat_count -- Hybrid: このエンドポイントが何度繰り返されるか
FROM USER_HISTOGRAMS
WHERE table_name = 'ORDERS'
AND column_name = 'STATUS'
ORDER BY endpoint_number;
-- Frequency ヒストグラムでは endpoint_number が累積頻度を表す
-- 例: endpoint_number=100, endpoint_value=1 → 値=1 が 100行ある
-- endpoint_number=150, endpoint_value=2 → 値=2 が 50行ある(150-100)
-- endpoint_number=300, endpoint_value=3 → 値=3 が 150行ある(300-150)
ヒストグラムの収集方法(METHOD_OPT)
DBMS_STATS.GATHER_TABLE_STATS の METHOD_OPT でヒストグラムを制御する
-- デフォルト: Oracle が自動で判断(偏りのある列に自動収集)
-- METHOD_OPT='FOR ALL COLUMNS SIZE AUTO' がデフォルト
EXEC DBMS_STATS.GATHER_TABLE_STATS(
ownname => 'HR',
tabname => 'ORDERS',
method_opt => 'FOR ALL COLUMNS SIZE AUTO' -- Oracle が自動でヒストグラムを収集
);
-- 特定列に指定バケット数でヒストグラムを収集する
EXEC DBMS_STATS.GATHER_TABLE_STATS(
ownname => 'HR',
tabname => 'ORDERS',
method_opt => 'FOR COLUMNS status SIZE 10' -- status 列に 10 バケットのヒストグラム
);
-- 複数列を指定する
EXEC DBMS_STATS.GATHER_TABLE_STATS(
ownname => 'HR',
tabname => 'ORDERS',
method_opt => 'FOR COLUMNS status SIZE 50 region SIZE 20'
);
-- ヒストグラムを収集しない(偏りがないと分かっている列)
EXEC DBMS_STATS.GATHER_TABLE_STATS(
ownname => 'HR',
tabname => 'ORDERS',
method_opt => 'FOR COLUMNS order_id SIZE 1' -- SIZE 1 = ヒストグラムなし
);
-- すべての列のヒストグラムをなくす
EXEC DBMS_STATS.GATHER_TABLE_STATS(
ownname => 'HR',
tabname => 'ORDERS',
method_opt => 'FOR ALL COLUMNS SIZE 1' -- 全列のヒストグラムを削除
);
-- SIZE AUTO: Oracle が列の使用状況(V$SQL_BIND_CAPTURE)と NDV から自動判断
-- SIZE SKEWONLY: データの偏りを検出した列にのみヒストグラムを収集
-- SIZE REPEAT: 既にヒストグラムがある列のみ更新する(新規収集はしない)
ヒストグラムとバインドピーキング・実行計画の崩れ
ヒストグラムはバインド変数を使ったクエリにも影響します。Oracle はバインド変数の初回実行時の値(バインドピーキング)を見て実行計画を決定します。偏りのあるデータで、よく使う値と稀な値で最適な実行計画が異なる場合、最初にどちらの値で実行されたかによって全セッションの実行計画が決まります。
バインドピーキングの問題を確認する
-- バインド変数を使ったクエリで実行計画が安定しない場合の調査
-- ① 問題が起きているクエリの実行計画を確認する
SELECT sql_id, child_number, executions, rows_processed,
is_bind_sensitive, -- YES: ヒストグラムがあり値によって最適計画が変わる可能性がある
is_bind_aware, -- YES: ACS(適応カーソル共有)が有効(12c 以降)
plan_hash_value
FROM V$SQL
WHERE sql_text LIKE '%SELECT%orders%WHERE%status%'
ORDER BY executions DESC;
-- ② DBMS_XPLAN で実行計画の詳細を確認する
SELECT * FROM TABLE(
DBMS_XPLAN.DISPLAY_CURSOR(sql_id => 'xxxx', cursor_child_no => 0, format => 'ALLSTATS LAST')
);
-- ③ ヒストグラムによる推定行数を確認する
-- 推定行数(E-Rows)と実際の行数(A-Rows)が大きく乖離している場合はヒストグラムの問題
-- 対処法:
-- ① ヒストグラムを再収集する(最新のデータ分布を反映する)
EXEC DBMS_STATS.GATHER_TABLE_STATS('HR', 'ORDERS', method_opt => 'FOR COLUMNS status SIZE AUTO');
-- ② バインドピーキングの影響を減らすためにリテラル値を使う(バインド変数を使わない)
-- ③ CARDINALITY ヒントで推定行数を手動指定する(最終手段)
SELECT /*+ CARDINALITY(o, 1000) */ * FROM orders o WHERE status = :1;
-- ④ BIND_AWARE ヒントで ACS を明示的に有効にする(12c 以降)
SELECT /*+ BIND_AWARE */ * FROM orders WHERE status = :1;
まとめ
- ヒストグラムの目的:データの偏り(一部の値が極端に多い・少ない)を統計に反映してカーディナリティ推定の精度を上げる。ヒストグラムがないと均等分布と仮定するため実行計画が崩れる
- 12c 以降の主要種類:Frequency(精度最高・NDV が小さい列)・Top-Frequency(頻出値が支配的な列)・Hybrid(それ以外の偏りある列)
- METHOD_OPT:FOR ALL COLUMNS SIZE AUTO がデフォルト。特定列は SIZE N でバケット数を指定、SIZE 1 でヒストグラムを無効化できる
- バインドピーキングの問題:ヒストグラムがある列にバインド変数を使うと初回の値で実行計画が固定される場合がある。ACS(IS_BIND_AWARE)が有効なら実行計画が複数管理される
- USER_HISTOGRAMS で確認:endpoint_value と endpoint_number でバケットの内容を確認できる。USER_TAB_COL_STATISTICS の histogram 列でどの種類が使われているかを確認する
DBMS_STATS によるオプティマイザ統計の管理全般については Oracle DBMS_STATS 完全ガイドを参照してください。実行計画の確認には Oracle DBMS_XPLAN 完全ガイドも活用してください。バインドピーキングと ACS の詳細は Oracle バインド変数完全ガイドも参照してください。