テックカリキュラム

実行計画の変換ルール(Rewrite Rules)の理論|SQL最適化の裏側

実行計画の変換ルール(Rewrite Rules)の理論|SQL最適化の裏側

SQLは宣言的言語であり、「どう実行するか」ではなく「何を取得するか」を記述します。しかし実際のDB内部では、クエリはそのまま実行されるわけではありません。

データベースはSQLを別の形に書き換え(Rewrite)、より効率的な実行計画へと変換します。この変換ルールこそが、クエリオプティマイザの核心です。

本記事では、SQLがどのように変換され、どのような理論に基づいて最適化されるのかを解説します。


■ クエリ処理の全体フロー

SQLは内部で以下のように処理されます:

SQL ↓ Parse(構文解析) ↓ Rewrite(書き換え) ↓ Optimize(最適化) ↓ Execute(実行) 

この中で「Rewrite」は、論理的に同値な別のクエリへ変換するフェーズです。


■ Rewrite Rulesとは何か?

Rewriteとは、SQLを意味を変えずに別の構造へ変換することです。

例:

SELECT * FROM A WHERE x IN (SELECT x FROM B); 

↓ 書き換え

SELECT A.* FROM A JOIN B ON A.x = B.x; 

この変換により、JOIN最適化が可能になります。


■ 関係代数による理論基盤

Rewriteは、関係代数(Relational Algebra)の法則に基づいています。

基本演算

  • 選択(Selection): ( \sigma )
  • 射影(Projection): ( \pi )
  • 結合(Join): ( \bowtie )

例:選択の移動(Selection Pushdown)

[ \sigma_{condition}(A \bowtie B) = (\sigma_{condition}(A)) \bowtie B ]

→ フィルタをJOIN前に適用することでデータ量削減


■ 主要なRewriteルール一覧

① Predicate Pushdown(述語プッシュダウン)

WHERE条件を可能な限り早い段階に移動。

-- Before SELECT * FROM A JOIN B ON A.id = B.id WHERE A.age > 30; -- After SELECT * FROM (SELECT * FROM A WHERE age > 30) A JOIN B ON A.id = B.id; 

効果: データ量削減 → JOINコスト低下


② Projection Pushdown

必要な列だけ早期に取得。

SELECT A.id FROM A JOIN B ON A.id = B.id; 

→ 不要な列を削除してI/O削減


③ Join Reordering(結合順序最適化)

[ (A \bowtie B) \bowtie C = A \bowtie (B \bowtie C) ]

結合順序を変えて最小コストを選択。

例:

  • A: 1,000,000行
  • B: 100行
  • C: 10行

→ (B JOIN C) → A とする方が効率的


④ Subquery Flattening(サブクエリの平坦化)

SELECT * FROM A WHERE x IN (SELECT x FROM B); 

SELECT DISTINCT A.* FROM A JOIN B ON A.x = B.x; 

効果: ネスト構造を解消し最適化可能に


⑤ Constant Folding(定数畳み込み)

WHERE price > 100 * 1.1 

WHERE price > 110 

コンパイル時に計算


⑥ Common Subexpression Elimination

同じ計算を1回にまとめる


⑦ View / CTE Inlining

ビューやCTEを展開して最適化対象にする

WITH tmp AS (SELECT * FROM A WHERE x > 10) SELECT * FROM tmp WHERE y > 5; 

SELECT * FROM A WHERE x > 10 AND y > 5; 

■ ルールベース vs コストベース

方式特徴
ルールベース(RBO)固定ルールで変換
コストベース(CBO)複数候補を評価して最適選択

現代DBは基本的にCBO(Cost-Based Optimizer)を採用。

Rewriteはその前段として、探索空間を広げる役割を持ちます。


■ 探索空間爆発問題

JOINが増えると、実行計画の組み合わせは爆発します:

[ n! (階乗) ]

例:10テーブル → 約360万通り

→ すべて試すのは不可能

そのため:

  • ヒューリスティック(経験則)
  • 動的計画法(Dynamic Programming)

で探索空間を削減します。


■ 実務での重要ポイント

① Rewriteされる前提でSQLを書く

SQLの見た目より「オプティマイザがどう解釈するか」が重要。


② CTEは最適化を阻害する場合がある

PostgreSQLではCTEが最適化フェンスになることがある。


③ サブクエリはJOINに変換されることが多い

パフォーマンス問題の原因になることも。


④ Rewriteされないケースを知ることが重要

  • 副作用関数
  • 非決定関数(random()など)
  • LIMIT / OFFSET

→ これらは変換が制限される


■ まとめ

  • Rewriteは「同値変換」による最適化の前段階
  • 関係代数の法則に基づく
  • Pushdown・Join順序・Flattenなどが重要
  • 探索空間爆発を抑えるためヒューリスティックが使われる

SQLチューニングの本質は、「SQLを書くこと」ではなく、DBがどう書き換えるかを理解することです。