SQLの一生(Parse → Optimize → Execute → Fetch)
- Parse: 構文・意味解析
- Optimize: 実行計画決定
- Row Source Generator: 実行可能形式に変換
- Execute: プラン実行
- 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倍以上変える。