SQLの一生(Parse → Optimize → Execute → Fetch)

  1. Parse: 構文・意味解析
  2. Optimize: 実行計画決定
  3. Row Source Generator: 実行可能形式に変換
  4. Execute: プラン実行
  5. Fetch: 結果返却
Optimizerはステップ②。ここで実行計画が決まる ⇒ 速いSQL / 遅いSQLの分岐点。

① Parseと共有プール(SQL Cache)

Parseのサブステップ

  • 構文解析: SQLが文法的に正しいか?
  • 意味解析: テーブル/列は存在するか? 権限は?
  • SQL文を一意に識別する「SQL_ID / プランハッシュ」を計算

共有プール(Shared Pool / Plan Cache)

  • 解析結果(実行計画)をSQL文の文字列と紐付けて保持
  • 同じSQLが再実行されたら、解析を省ける
  • LRUで管理。古いプランは追い出される
Parseをいかに省くかがOLTPの肝。

共有プールの内訳:

  • Library Cache: SQL文 + 実行計画
  • Data Dictionary Cache: 表定義・統計情報
  • Result Cache: 結果セット(一部DB)

ハードパースとソフトパース

Hard Parse(遅い)

共有プールに該当する計画がない状態。

  • 構文解析・最適化を全部やる
  • 統計情報からコスト計算する
  • Latch/Mutexの競合が起きやすい
  • CPU・メモリを多く消費する

Soft Parse(速い)

共有プールにヒット ⇒ 計画を再利用。

  • 構文チェックと権限チェックのみ
  • 最適化はスキップ
  • OLTPでは大半をこちらにしたい
  • 「No Parse」ならさらに速い(カーソル保持)

バインド変数の効果

-- ハードパースを毎回呼ぶ
SELECT * FROM orders WHERE id = 1;
SELECT * FROM orders WHERE id = 2;
SELECT * FROM orders WHERE id = 3;

-- ソフトパース (計画再利用)
SELECT * FROM orders WHERE id = :id;
-- バインド: :id = 1, 2, 3 ...

② Optimizerの仕事(CBO)

コストベース・オプティマイザ(CBO)

  • 「最も少ない見積りコスト」で取れる計画を選ぶ。
  • コストは I/O回数・CPU・メモリ・ネットワーク を単位化したもの。
  • 統計情報(行数・カーディナリティ・NDV・ヒストグラム・索引)を元に推定する。

決める内容

  • どのインデックスを使うか / FULL SCANするか
  • どんなJOIN方式か(Nested / Hash / Merge)
  • JOIN順序(a→b→c か c→b→a か)
  • 並列度・パーティション枝刈り
候補プランから最小コストの1つを採用する。
例: 案A Full Scan + Hash Join (cost 250) / 案B Index Range + Nested Loop (cost 80) ← 採用 / 案C Index FFS + Merge Join (cost 140)

③④ 実行時に使われる領域

SQL実行中、DBは複数の領域を同時に使います。

  • Buffer Cache: テーブル/索引のブロックを読込・更新。物理I/Oが必要ならディスクから読む。
  • PGA(作業域): ソート・ハッシュ結合・集約の作業領域。セッション毎に確保。
  • TEMP: PGAで足りない時の溢れ先。ディスクに書き出される(Spill)。
  • UNDO: 更新前のイメージを保存。ROLLBACKや一貫性読みに使う。
  • REDO Log Buffer: 変更履歴を残す。コミット時にLGWRが書き出す。
  • 共有プール: 実行中も計画情報を参照。結果キャッシュが効くこともある。
更新系SQLは「Buffer Cacheを書き換える + REDO Log Bufferに履歴を残す」が基本動作。

⑤ Fetchとクライアントへの返却

  • サーバプロセスはまず「最初の数行」を生成して返す(パイプライン)。
  • 全件を一気に返すわけではなく、アプリ側が「次の行をください」と要求するたびにサーバが行を生成 ⇒ ネットワーク転送 ⇒ クライアントバッファへ。
  • 配列フェッチ(Array Fetch)のサイズを増やすと往復回数が減って高速化する。
  • カーソルを閉じずに使い回すと、Parseのオーバーヘッドも消せる。
Optimizerは①〜⑤全体のうち②しか担当しないが、その判断が ④の実行コストを10倍以上変える。