ALGORITHM VISUALIZER

ルールベース最適化(Rule-Based Optimization)

データベースのクエリを固定ルールで書き換えて高速化する、古典的なクエリ最適化手法

シナリオ選択
ルール一覧
P1
Selection Pushdown
WHERE条件をJOINの前に移動し、早い段階で行数を減らす
P2
Projection Pushdown
不要なカラムをできるだけ早く除外し、処理するデータ量を減らす
P3
Predicate Simplification
冗長な条件式を簡略化する(例: x > 5 AND x > 3 → x > 5)
P4
Constant Folding
定数式を事前計算する(例: WHERE price > 100 * 1.1 → WHERE price > 110)
P5
Join Elimination
結果に影響しない不要なJOINを除去する
P6
Subquery Unnesting
サブクエリをJOINに書き換えて効率化する
P7
View Merging
ビュー参照をベーステーブルのクエリに展開する
P8
OR to UNION
OR条件をUNIONに変換してインデックスを活用可能にする
統計
0
適用済
8
全ルール数
0
変換回数
凡例
SCAN (テーブル読取)
FILTER (WHERE条件)
PROJECT (SELECT)
JOIN (結合)
SORT (ソート)
変換済みノード
変換ログ
変換なし — ルールを適用してください
QUERY TREE0/8 RULES APPLIED
解説

📌
ルールベース最適化とは

ルールベース最適化(Rule-Based Optimization, RBO)は、データベースのクエリを高速に実行できる形に書き換える手法の一つです。 初期のデータベース製品(Oracle v7以前、初期のMySQL)で主に採用されていました。現在はコストベース最適化(CBO)が主流ですが、RBOの概念は今でもCBOの中に部品として残っています。

RBOの最大の特徴は、テーブルの統計情報(行数、カーディナリティなど)を一切使わないことです。 代わりに「Selection Pushdownは常に良い」「Projection Pushdownは常に良い」といった固定のルール(書き換え規則)を優先度順に機械的に適用します。

中でも最も重要なルールはSelection Pushdown(選択プッシュダウン)です。 これはWHERE条件をクエリツリーの下の方(データソースに近い側)に移動させることで、JOINなどの重い処理に渡す行数を早い段階で削減する手法です。 例えば100万行のテーブル同士をJOINした後にWHEREで絞るのではなく、先にWHEREで1000行に絞ってからJOINすれば、処理量は劇的に減ります。

🔍
このツールの値は何を表している?

上のビジュアライザーに表示されているツリーは、SQLクエリの実行計画(クエリツリー)を表しています。 各ノードはクエリの中の一つの処理ステップに対応します。

SCANテーブルからデータを読み取る操作。SQLの FROM 句に対応。「Table Scan: products」は products テーブル全体を読むことを意味する。
FILTERWHERE 条件による行の絞り込み。「WHERE price > 100」なら price が100を超える行だけを残す。ツリーの下に移動させるほど、後続処理のデータ量が減って高速になる。
PROJECTSELECT 句によるカラムの選択。「SELECT name, price」なら name と price だけを取り出す。不要なカラムを早期に除外するとメモリ使用量が減る。
JOIN2つのテーブルの結合。最もコストの高い操作の一つ。入力行数が N×M に膨張するため、JOINの前にFILTERで行数を減らすことが極めて重要。
SORTORDER BY によるソート。データ量に対して O(n log n) のコストがかかる。

Selection Pushdown のイメージ

BeforePROJECTFILTER (WHERE)JOINSCAN (users)SCAN (orders)SelectionPushdownAfterPROJECTJOINFILTER (WHERE)← moved down!SCAN (users)SCAN (orders)FILTERをJOINの下に移動させ、早期にデータ量を削減する

⚙️
仕組み

料理のレシピを効率化するイメージです。レシピに「野菜を全部洗って、全部切って、必要なものだけ選んで料理する」と書いてあったら、「必要なものだけ先に選んでから洗って切る」方が効率的ですよね? ルールベース最適化はこのような「明らかに効率的な書き換え」をルールとして持っています

主なルールと料理での例え

Selection Pushdown= 使わない食材は最初に除外する

WHERE条件をツリーの下に移動し、不要な行をJOINの前に除外します。100万行をJOINしてから絞るのではなく、先に1000行に絞ってからJOINします。

Projection Pushdown= 必要な調味料だけ準備する

不要なカラムをできるだけ早い段階で捨てます。10カラムあっても使うのが2カラムなら、最初から2カラムだけ持ち運びます。

Constant Folding= レシピの「大さじ1 + 大さじ1」を事前に「大さじ2」にまとめる

WHERE price > 50 + 50 のような定数式を、実行前に WHERE price > 100 に変換します。実行時に毎回計算する無駄を省きます。

ルールベースは「こうしたら必ず速くなる」という確実なルールだけを適用するため、テーブルの大きさを知らなくても使えます。ただし、テーブルの大きさによって最適解が変わるケース(seq_scan vs index_scan)には対応できません。そのような判断には、統計情報を使うコストベース最適化(CBO)が必要になります。

最適化の流れ

1
SQLをパースしてクエリツリー(論理計画)を構築
2
ルールを優先度の高い順にソート(P1 → P2 → ... → P8)
3
各ルールをツリーに対してパターンマッチ
4
マッチしたらツリーを書き換える(変換)
5
すべてのルールを適用し終えたら完了

Selection Pushdownの具体例

Before(最適化前)
PROJECT (name, price)
└ FILTER (price > 100)
  └ JOIN (orders × products)
    ├ SCAN (orders) ← 100万行
    └ SCAN (products) ← 10万行
JOIN後にフィルタ → 100万×10万の結合が先に走る
After(最適化後)
PROJECT (name, price)
└ JOIN (orders × products)
  ├ SCAN (orders) ← 100万行
  └ FILTER (price > 100)
    └ SCAN (products) ← 1000行に削減
フィルタを先に実行 → JOINの入力が1000行に

特徴

  • 📏決定論的同じクエリには常に同じ変換を適用する。統計情報に依存しないため、結果が予測可能で、デバッグが容易。
  • 高速各ルールのパターンマッチと書き換えは O(n)(ツリーのノード数に比例)。コストベースのように複数の実行計画を生成して比較する必要がない。
  • 🧱実装がシンプルルールを追加・削除するだけで拡張できる。各ルールは独立しており、他のルールの知識を必要としない。
  • ⚠️統計を使わない弱点「テーブルが小さいからフルスキャンの方が速い」といった判断ができない。インデックスの有無やデータ分布を考慮できない。
  • 🏛️歴史的意義Oracle v7以前、初期のMySQL、SQLiteの一部で使われた。現在のCBOにもRBOのルールが「ヒューリスティック段階」として組み込まれている。

💡
ユースケース

🏛️ レガシーDBの保守
Oracle v7以前やMySQL 3.x系など、RBO時代のシステムを理解・保守する際に必須の知識。
📐 CBOのヒューリスティック段階
現代のCBOでも、明らかに有利な書き換え(Selection Pushdownなど)はRBOのルールとして先に適用される。
🛠️ 組み込みDB・軽量DB
SQLiteなど統計収集のオーバーヘッドを避けたい軽量DBでは、RBO的なルールが今でも中心。
📚 クエリ最適化の学習
CBOを理解する前に、まずRBOで「どんな書き換えが有効か」を学ぶのが定石。本ツールはそのための教材。

📖
用語解説

Selection Pushdown(選択プッシュダウン)[P1]
WHERE条件(SELECT文の行の絞り込み)を、クエリツリーの下方(データソースに近い位置)に移動させるルール。 最も効果が大きいルールとされ、優先度1が割り当てられる。JOINの前にFILTERを移動することで、結合対象の行数を大幅に減らせる。 例えば「100万行 JOIN 10万行 → FILTER」を「100万行 JOIN (FILTER → 1000行)」に変えると、処理量が数百倍以上削減される。
Projection Pushdown(射影プッシュダウン)[P2]
SELECT句で必要なカラムだけをできるだけ早い段階で抽出するルール。 テーブルに50カラムあるが最終的に必要なのは3カラムだけ、という場合に、JOINやSORTの前に3カラムに絞り込む。 これにより中間結果のメモリ使用量とI/O量が大幅に減少する。SELECT * を避けるべき理由の根拠でもある。
Predicate Simplification(述語簡略化)[P3]
冗長な条件式を簡略化するルール。例えば「WHERE x > 5 AND x > 3」は「WHERE x > 5」に置き換えられる(x > 5 なら x > 3 は常に真)。 「WHERE 1 = 1 AND price > 100」は「WHERE price > 100」に。不要な条件評価を除去して、CPU使用量を削減する。
Constant Folding(定数畳み込み)[P4]
定数同士の演算をコンパイル時(最適化時)に事前計算するルール。 「WHERE price > 100 * 1.1」を「WHERE price > 110」に変換する。実行時に毎行100 * 1.1を計算する無駄を排除する。 コンパイラの定数畳み込み最適化と同じ概念をSQL最適化に応用したもの。
Subquery Unnesting(サブクエリの非ネスト化)[P6]
IN句やEXISTS句のサブクエリをJOINに書き換えるルール。 「WHERE id IN (SELECT id FROM t2)」は「JOIN t2 ON t1.id = t2.id」に変換できる。 サブクエリは外側のクエリの各行に対して繰り返し実行される可能性があり(correlated subquery)、O(n^2)になりうる。 JOINに変換すればハッシュ結合などの効率的なアルゴリズムが使え、O(n log n)や O(n)に改善される。

📋
手順

  1. 1
    SQLパーサーがクエリを解析し、クエリツリー(論理演算子のツリー)を構築する。ツリーのルートは最終出力(PROJECT)、葉はテーブルスキャン(SCAN)。
  2. 2
    オプティマイザがルールセットを優先度順にソートする。Selection Pushdown(P1)が最も高い優先度を持つ。
  3. 3
    P1のルールからツリーを走査し、パターンにマッチするサブツリーを探す。例えば「JOINの上にFILTERがある」パターン。
  4. 4
    マッチしたら、ルールに従ってサブツリーを書き換える。FILTERノードをJOINの下に移動する。
  5. 5
    P2、P3...と順番にすべてのルールを適用する。各ルールは1回だけ適用される。
  6. 6
    すべてのルールの適用が完了したら、書き換え後のクエリツリーが最終的な実行計画として使われる。

⚖️
Rule-Based vs Cost-Based 比較

観点Rule-Based (RBO)Cost-Based (CBO)
判断基準固定ルール(優先度順)コストモデル(I/O, CPU, メモリ推定値)
統計情報使わないテーブル行数、カーディナリティ、ヒストグラムなどを使用
結果の予測性常に同じ結果(決定論的)統計が変わると実行計画が変わりうる
最適性データ分布を考慮できず、最適とは限らないより正確にコストを推定し、最適に近い計画を選択
実装コストシンプル・軽量統計収集・コストモデル・探索空間の管理が必要
代表的なDBOracle v7以前、MySQL 3.x、SQLiteOracle 7+、PostgreSQL、MySQL 5.x+、SQL Server
現在の位置づけCBOのヒューリスティック段階として残存主流。ほぼすべての商用・OSSのRDBMSが採用

現代のデータベースでは、CBOが主流です。しかしCBOの内部でも、明らかに有利な変換(Selection PushdownやConstant Foldingなど)は ヒューリスティック段階としてRBOのルールが先に適用されます。RBOを学ぶことは、CBOの理解にも直結します。

関連コンテンツ