コンピューターサイエンス:アルゴリズム / OS / ネットワーク / DB

クエリプランナの役割

生田 陸人
LuaGate エンジニア / 現役エンジニア
編集 LuaGate編集部

クエリプランナの役割

このレッスンで分かること

  • SQL から実行計画が生成されるまでの 4 ステップ
  • コストベースとルールベースの違い
  • プランナが「賢く」見える理由と限界

クエリプランナの役割 とは

SQL から実行計画が生成される過程を追う。本レッスンでは、クエリプランナの役割 の基本から実際の使いどころまでを整理し、現場で迷わず使える形に落とし込みます。

SQL は「何を欲しいか」だけ書く

SQL は 宣言型言語 です。SELECT name FROM users WHERE age > 30 ORDER BY id と書くだけで、「どうやって取り出すか」は書きません。これを どう実行するか を決めるのが クエリプランナ (Query Planner) です。同じ SQL でも、データ量・インデックス・統計情報によって全く違う実行計画が選ばれます。

クエリ実行の 4 ステップ

diagram (will load when visible)

1. パース — 文字列を AST(抽象構文木)に変換。構文エラーをここで検出。

2. リライト — ビューを実体テーブルに展開、サブクエリを JOIN に変換するなど、論理的に等価な書き換えを行う。

3. プランニング — 複数の実行候補(プラン)を生成し、コストを見積もって最安のものを選ぶ。

4. 実行 — 選ばれたプランに従って、テーブルを読み・JOIN し・ソートし・集計し・結果を返す。

コストベース最適化

主要 DB は コストベース最適化 (CBO) を採用しています。各候補プランに「コスト見積もり」を計算し、最も安いものを選びます。

コストは次の要素で計算されます。

  • 読むべき ページ数(ディスク I/O 推定)
  • フィルタを通過する 行数 見積もり
  • ソート・ハッシュに必要な メモリ量
  • CPU 命令数

これらは 統計情報 から推定されます。テーブルの行数、各カラムの値の分布、NULL 率、ヒストグラム、などです。統計情報が古いと見積もりが外れ、悪いプランが選ばれます。

プランナの賢さは「統計情報の鮮度」次第。だから ANALYZE を定期実行する。

候補プランの例

SELECT * FROM orders WHERE user_id = 100 ORDER BY created_at LIMIT 10 を考えます。

候補プランは少なくとも 3 通りあります。

  • A. user_id インデックスで絞り込み、メモリ上でソート
  • B. created_at インデックスで降順スキャンしながら user_id=100 を探す
  • C. (user_id, created_at) 複合インデックスを使い、絞り込み + ソート両方をインデックスで完結

user_id=100 の行が全体の 1% なら A 候補も妥当ですが、複合インデックス C が圧倒的に速いです。プランナはコストを計算して C を選びます。

ルールベース最適化(RBO)の遺産

古い Oracle や一部の DB は ルールベース最適化 (RBO) を使っていました。「インデックスがあれば常に使う」「ORDER BY がついてたらソートする」など固定ルールでプランを決めます。シンプルですが、データの偏りに対応できないので主流ではなくなりました。

プランナの限界

CBO も万能ではありません。次のケースで間違ったプランを選びがちです。

  • 値の偏り(一部の値だけ極端に多い) — ヒストグラム統計があれば対応できるが、サンプリングで取り切れないこともある
  • 複数カラムの相関country='JP' AND city='Tokyo' のような暗黙の関係) — 通常の統計では独立と仮定するので、行数推定が外れる
  • パラメータスニッフィング — プリペアドステートメントで、初回値に最適化したプランが他の値に合わない
  • 再帰クエリや CTE — 推定が困難で誤差が大きい

これらの場合、開発者は クエリヒント書き換え で誘導します。

クエリヒント

SQL クエリ

-- MySQL SELECT /*+ INDEX(orders idx_user_created) */ * FROM orders WHERE user_id = 100; -- PostgreSQL は標準ヒントなし。pg_hint_plan 拡張で可能

ヒントは強力ですが、データが変わると裏目に出ることもあります。多用は禁物で「プランナが選び損なう場面の応急処置」として使います。

EXPLAIN で見る

実行計画は EXPLAIN で確認できます。

SQL クエリ

EXPLAIN SELECT * FROM users WHERE age > 30;

これは次のレッスンで深掘りします。プランナの判断を可視化する一番大事なコマンドです。

計画キャッシュ

同じ SQL を何度も実行する場合、プランニングを毎回やると CPU の無駄です。多くの DB は プランキャッシュ を持ち、同じ SQL の計画を再利用します。

PostgreSQL の PREPARE / EXECUTE や MySQL の Prepared Statement は明示的にキャッシュを使う仕組みです。アプリ ORM もこれを内部で利用しています。

同じ SQL を文字列単位でキャッシュするので、空白や定数値が変わると別物扱いになる。プレースホルダで書くのが定石。

やってみよう

  • 自分のプロジェクトの遅いクエリに EXPLAIN をかけて、出力を読んでみる
  • ANALYZE TABLE で統計情報を更新して、プランが変わるか観察する
  • 同じ SQL を 2 回実行し、2 回目が速いかを計測(キャッシュの効果)

よくある質問

Q. このトピックは実務でどう役立ちますか?

A. DB のクエリ最適化、API 設計、データ構造の選択など、設計判断の根拠になります。表面的にライブラリを使うだけでなく「なぜそれが速いのか」を理解できると、性能問題を未然に防げます。コーディング面接でも頻出のテーマです。

Q. 統計情報はいつ更新すべきですか?

A. テーブルへの大量 INSERT/UPDATE/DELETE の後や、夜間バッチ処理が終わったタイミングが目安です。PostgreSQL では ANALYZE、MySQL では ANALYZE TABLE を実行します。多くの DB は自動 ANALYZE を持ちますが、急激なデータ変化の後は手動実行を検討してください。

Q. EXPLAIN の出力で最初に確認すべき箇所はどこですか?

A. 推定行数(rows)と実際の行数の乖離、およびアクセスタイプ(Seq Scan か Index Scan か)です。推定が大きく外れている場合は統計情報の更新が必要なことが多く、Seq Scan が出ている場合はインデックスの追加や見直しを検討します。

次のレッスン

次は EXPLAIN の読み方 で、実行計画の出力を実際に読み解く方法を学びます。

事前確認 — 進む前に次の 3 つができることを確認しましょう。

  1. クエリプランナの役割 の要点を自分の言葉で説明できる
  2. このレッスンの最小コード (または操作手順) を見ずに書ける
  3. 練習問題やクイズで間違えた箇所を読み直して理解した

理解度チェック (30 秒)

Q. クエリプランナの役割 とは何か、1 文で説明してください。

この章のポイント

A. 本文の「このレッスンで分かること」または冒頭の説明文を見直し、自分の言葉で要約できれば OK。詰まったら本レッスンの最初の H2 セクションを読み返してみましょう。

関連レッスン