TL

クエリオプティマイザの内部

EXPLAIN の数字がどう決まるのかを根本から理解できます。パースから論理・物理プラン、コスト見積もり、探索空間の枝刈りまでを原理から追います。

応用クエリオプティマイザコストベース最適化統計情報実行計画結合順序最終更新: 2026-06-21
TL;DR要点だけ先に
  • 1.SQL は パース→論理プラン→物理プラン→実行 と段階変換され、論理プランは「何を」、物理プランは「どう」取るかを表す。
  • 2.コストベース最適化は統計情報から各候補プランの推定コスト(I/O + CPU の見積もり)を計算し、最小コストの物理プランを選ぶ。
  • 3.結合順序の候補は組合せ爆発するため、動的計画法やヒューリスティックで探索空間を枝刈りして現実的な時間に収める。

オプティマイザは何を決めているのか

同じ結果を返す SQL でも、表をどの順で結合し、各表をどの方式で読むかによって速度は桁違いに変わります。クエリオプティマイザは、論理的に等価な無数の実行方法の中から、推定コストが最小のものを1つ選ぶコンポーネントです。EXPLAIN(→ クエリ最適化)で見える実行計画は、この選択の最終結果にすぎません。本稿では「どう選ばれるか」を内部から追います。

4段階の変換パイプライン

オプティマイザを含む処理系は、SQL を次の順で変換します。

段階入力→出力決めること
パース/検証SQL 文字列 → 構文木(AST)構文の正しさ、表・列の存在と型の解決
論理プラン生成AST → 関係代数の演算子木何を取るか(選択・射影・結合・集約)
物理プラン生成論理プラン → 物理演算子木どう取るか(走査方式・結合アルゴリズム・順序)
実行物理プラン → 結果行実際にデータを読み、行を生成

論理プランは関係代数の木で、ノードは選択(σ)・射影(π)・結合(⋈)・集約などの論理演算子です。これは「意味」だけを表し、まだアルゴリズムを含みません。一方、物理プランの各ノードは Seq Scan / Index Scan / Hash Join / Merge Join のような物理演算子で、CPU とディスクが実際に行う手続きに対応します。1つの論理演算子(例:結合)は複数の物理演算子(Nested Loop / Hash / Merge)に展開でき、ここに選択の自由度が生まれます。

論理プランの書き換え(等価変換)

物理化の前に、オプティマイザは関係代数の等価規則でプランを整形します。結果を変えずに、後段が扱いやすい・コストが下がりやすい形へ寄せる前処理です。

  • 述語のプッシュダウン: σ(選択)を結合より下、表の走査直後へ押し下げ、早い段階で行数を絞る。
  • 射影のプッシュダウン: 不要な列を早く落とし、中間結果の幅を狭める。
  • 結合の交換・結合律: A ⋈ B = B ⋈ A(A ⋈ B) ⋈ C = A ⋈ (B ⋈ C) を使い、後の探索で結合順序を自由に組み替えられるようにする。
  • 副問い合わせの平坦化: 相関サブクエリを結合(semi join / anti join)へ書き換え、N+1 的な反復を避ける。
変換ベースとコストベースは別物

等価変換そのものは「結果を変えない」ための規則で、どれが速いかは判断しません。どの形・どの物理演算子が最小コストかを決めるのは次節のコスト見積もりです。多くの実装ではこの2つが連携し、変換で広げた候補をコストで採点します。

コストベース最適化の中身

物理プランの候補それぞれに対し、オプティマイザは推定コストを計算します。コストは実時間(ms)ではなく、I/O と CPU の作業量を見積もった相対値です。PostgreSQL を例にすると、概念的には次の合算です。

推定コスト ≈ (読むページ数 × ページI/O単価)
           + (処理する行数 × 行あたりCPU単価)
           + (評価する式の数 × 式CPU単価)

各単価はパラメータ(seq_page_costrandom_page_costcpu_tuple_cost など)で、ランダム読みは逐次読みより高く見積もられます。重要なのは、これらの式に入る「ページ数」「行数」が統計情報からの推定値だという点です。つまりコスト計算の精度は、後述する統計の鮮度と精度に完全に依存します。

オプティマイザは候補プランを木の下から組み立て、部分プランごとに「コスト」と「出力推定行数(カーディナリティ)」を伝播させます。親ノードのコストは子のコストと自分の処理コストの和で決まるため、下流の行数推定が狂うと上流まで誤差が増幅します。

統計情報とカーディナリティ推定

統計情報は、各列の値の分布を要約したメタデータです。代表的に次を保持します。

統計内容使われ方
行数・ページ数表全体の規模走査コストの基準
n_distinct列の異なる値の個数等値条件の選択率推定
ヒストグラム値域を等頻度の区間に分割範囲条件の選択率推定
MCV(最頻値)頻出値とその出現比率偏った値の選択率を精密化
相関列の物理的な並び順との相関Index Scan のランダムI/O量を補正

選択率(selectivity) は、ある条件が全体の何割を通すかの推定値です。例えば n_distinct が 1000 の列に等値条件を掛ければ、一様分布の仮定では選択率は約 1/1000、推定出力行数は「全行数 ÷ 1000」となります。範囲条件はヒストグラムの該当区間を数えて推定します。これらを掛け合わせて結合後のカーディナリティを見積もるのが、最適化の数値的な土台です。

独立性の仮定が誤差の主因

複数条件 WHERE a = 1 AND b = 2 の選択率を、多くの実装は各列の選択率の積として推定します。しかし ab に相関があると(例:郵便番号と都道府県)、実際の行数は推定から大きくずれます。結果として誤った結合順序やアルゴリズムが選ばれ、計画が破綻します。複数列統計(拡張統計)の作成や ANALYZE の実行で緩和します。

探索空間と枝刈り

最大の難所は結合順序です。N 個の表の結合順序の候補数は、木の形まで含めると N とともに階乗的に増えます(結合木の総数は超指数的)。全列挙は数十表で非現実的になるため、探索空間を縮める仕組みが必須です。

代表的なのが System R 由来の**動的計画法(DP)**です。要点は「部分問題の最適解を再利用する」こと。

# ボトムアップDPの骨子(概念)
1. 各表の最良アクセス方式(Seq / Index)を確定
2. サイズ2の全部分集合について最良の結合プランを求めて表に記録
3. サイズkのプランを、サイズk-1の最良プラン+1表 から合成
4. 最後に全表を含む集合の最良プランを採用

DP は同じ部分集合(例:{A,B})の最適プランを一度だけ計算して共有するため、素朴な全列挙より大幅に小さい計算量で済みます。それでも表数が増えると DP 表が爆発するため、さらに次の枝刈り・近似を併用します。

  • 興味のある順序(interesting orders): ある結合や ORDER BY で後段が活かせる並び順を持つ部分プランは、コストが多少高くてもソート省略の見込みで残す。これにより Merge Join の機会を逃さない。
  • 劣プランの即時破棄: 同じ部分集合・同じ出力順序で、より高コストのプランは捨てる(支配関係による枝刈り)。
  • 探索の打ち切り: 表数がしきい値(PostgreSQL の geqo_threshold など)を超えると、DP をやめ遺伝的アルゴリズムなどのヒューリスティック探索に切り替え、最適でなくとも十分良い解を現実的な時間で得る。
試験・面接での問われ方

「オプティマイザが結合順序を全列挙しない理由」を問われたら、候補数が表数に対し階乗的(結合木では超指数的)に増えるため、と答えます。対策として動的計画法による部分問題の共有と、表数が多い場合のヒューリスティック(遺伝的アルゴリズム等)への切替を挙げると的確です。

物理演算子の選択例

論理的な1つの結合に対し、物理化では複数アルゴリズムが候補になります。どれが最小コストかは入力サイズと統計で決まります。

結合アルゴリズム得意な状況コストの効き方
Nested Loop外側が小さく内側に索引がある外側行数 × 内側索引引きのコスト
Hash Join等値結合で片側がメモリに収まる両表を1回ずつ走査+ハッシュ構築
Merge Join両入力が結合キー順に並ぶソート済みなら走査のみ、未ソートならソート費用が乗る

例えば結合の片側が数十行なら Nested Loop(→ JOIN の仕組み)が、両側が大きく等値結合なら Hash Join が選ばれやすくなります。入力が既にキー順なら Merge Join がソートを省けて有利です。ここでも判断材料は統計由来の推定行数であり、推定が外れると不適切な演算子が選ばれます。

まとめ:EXPLAIN を原理から読む

オプティマイザは、SQL を関係代数へ落とし、等価変換で形を整え、統計から各候補のコストを見積もり、枝刈り付きの探索で最小コストの物理プランを選びます。したがって EXPLAIN ANALYZE推定行数(estimated rows)と実測行数(actual rows)が大きく食い違う箇所を見つけたら、それは統計の誤差が悪い計画を招いているサインです。ANALYZE での統計更新や拡張統計の追加が、ヒントの乱用よりも本質的な対処になります。並行する書き込みとの兼ね合い(→ ロックと MVCC)や、巨大表の分割(→ パーティショニング)も、最終的にはオプティマイザが扱う統計とコストを通じて計画に効いてきます。

データベース Article

クエリオプティマイザの内部を実務で読む

TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。

解決すること

クエリオプティマイザ

比較で見る軸

難易度: advanced / カテゴリ: データベース / タグ数: 5

導入後に効く点

コストベース最適化は統計情報から各候補プランの推定コスト(I/O + CPU の見積もり)を計算し、最小コストの物理プランを選ぶ。

先に潰すリスク

用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。

数字・仕様の読み方
難易度
advanced
カテゴリ
データベース
タグ数
5

判断チェックリスト

  • 自社の用途が「クエリオプティマイザ / コストベース最適化」に近いか確認する。
  • 強みである「SQL は パース→論理プラン→物理プラン→実行 と段階変換され、論理プランは「何を」、物理プランは「どう」取るかを表す。」が本当に評価軸になるか確認する。
  • 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
  • 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
  • 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
  • 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。

次に確認する観点

クエリオプティマイザコストベース最適化統計情報実行計画結合順序クエリオプティマイザコストベース最適化統計情報
参考: 公式情報