TL

プランキャッシュとパラメータスニッフィングの内部

同じクエリが時々だけ激遅になる謎を解けます。プラン再利用の仕組みと、特定パラメータ向けプランが他値で劣化する原理、再コンパイルやヒントでの対処までを追います。

応用プランキャッシュパラメータスニッフィングプリペアドステートメント実行計画統計情報最終更新: 2026-06-21
TL;DR要点だけ先に
  • 1.プリペアドステートメントはパース・最適化の結果をキャッシュし、実行のたびにコスト見積もりを省く再利用機構である。
  • 2.パラメータスニッフィングは、初回コンパイル時の引数値に最適化されたプランが、分布の偏った別の値では破滅的に遅くなる現象を指す。
  • 3.対策はパラメータごとの再最適化、リテラル埋め込み、ヒント固定、プラン安定化機能で、いずれもキャッシュ率と最適性のトレードオフを調整する。

同じクエリがなぜ突然遅くなるのか

何も変えていないのに、ある日から特定のクエリだけが桁違いに遅くなる。これはバグでも負荷でもなく、プランキャッシュが誤った実行計画を握り続けているサインであることが多いです。データベースは毎回ゼロから最適化していては高並行のワークロードに耐えられないため、一度作った計画を再利用します。その再利用の仕組みそのものが、条件次第で性能事故を引き起こします。本稿はその原理と対処を内部から追います。

プリペアドステートメントとプラン再利用

1つの SQL を実行可能にするには、パース・検証・論理プラン生成・物理プラン生成という重い工程(→ クエリオプティマイザの内部)を経ます。同じ形のクエリを値だけ変えて何度も実行するとき、この工程を毎回繰り返すのは無駄です。プリペアドステートメントは、SQL の骨格を一度だけコンパイルし、可変部分を**プレースホルダ(バインド変数)**として残しておく仕組みです。

-- prepare: 骨格を1回だけコンパイルし、値は $1 として残す
PREPARE q AS SELECT * FROM orders WHERE customer_id = $1;
-- execute: 値だけ差し替えて再利用(最適化を省略)
EXECUTE q(42);
EXECUTE q(99);

ポイントは、キャッシュのキーがSQL文字列の正規化形である点です。WHERE customer_id = $1 という骨格が一致すれば、42 でも 99 でも同じキャッシュ済みプランが引かれます。これにより最適化コストはほぼゼロになり、CPU と内部ロック(プランキャッシュ自体への競合)を大きく節約できます。アプリのドライバが自動でプリペアド化する場合や、ストアドプロシージャ(→ ストアドプロシージャ)として登録する場合も、内部の再利用機構は同じです。

キャッシュキーは「値ではなく形」

プランキャッシュは値ごとに別プランを持つのではなく、パラメータ化された「形」ごとに1つのプランを持ちます。だからこそ再利用が効きますが、同時にこれが次節の問題の根本原因になります。1つの形に1つのプランしか結びつかないため、値による最適プランの違いを吸収できないのです。

パラメータスニッフィングの原理

プランをコンパイルする瞬間、オプティマイザは「いま渡された具体的な値」を覗き見て(sniff)、その値に最適な計画を立てます。これがパラメータスニッフィングです。スニッフィング自体は正しい戦略です。値が分かれば統計情報(ヒストグラムや最頻値)を使って正確な選択率を見積もれる(→ カーディナリティ推定とヒストグラム・スケッチ)からです。問題は、その初回の値に最適化されたプランが、以降のすべての実行で再利用されることにあります。

値の分布が大きく偏っている列では、これが破滅を招きます。次の例を考えます。

初回スニッフ値オプティマイザの判断後続の別値での挙動
稀な値(数十行ヒット)Index Scan + Nested Loop が最適頻出値が来ると数百万行を1行ずつ索引引き → 激遅
頻出値(数百万行ヒット)Seq Scan + Hash Join が最適稀な値が来ても全表走査 → 無駄な大量I/O

どちらの値で先にコンパイルされたかという偶然で、キャッシュに焼き付くプランが決まります。サーバー再起動・統計更新・キャッシュ追い出しのたびに「次に誰が最初に引くか」でプランが変わるため、再現性が低く、原因の特定が難しいのが特徴です。

平均向けプランは誰も幸せにしないことがある

「偏りがあるなら平均的な値で最適化すればよい」と考えがちですが、平均向けプランは稀な値にも頻出値にも中途半端で、どちらの実行でも最適より遅くなることがあります。最適プランが値域で不連続に切り替わる(ある選択率を境に Index Scan と Seq Scan が逆転する)ため、単一プランでは本質的にカバーしきれないのです。

推定が外れる量を見積もる

なぜ遅延が「数倍」でなく「数千倍」になるのかは、推定行数と実測行数のズレで説明できます。Nested Loop は外側の各行ごとに内側を引くため、外側の行数推定が 1 件のつもりが実際 1,000,000 件なら、内側の索引引きが百万回走り、コストはほぼ百万倍に膨らみます。EXPLAIN ANALYZE でこのズレを直接観測できます。

-- 稀な値向けにコンパイルされたプランを、頻出値で実行した場合
Nested Loop  (rows=1 ...)            -- 推定: 外側1行
  -> Index Scan on orders (rows=1)   -- 実測: 実は 1,200,000 行
  -> Index Scan on items  (loops=1,200,000)  -- 内側が百万回反復

estimated rowsactual rows、そして Nested Loop の loops が極端に食い違っていれば、それがパラメータスニッフィングの動かぬ証拠です。プラン構造そのものは正しく、ただ前提とした入力サイズが現在の値と一致していないだけなのです。

対策の系統と効き方

対処はいずれも「キャッシュの再利用率」と「各実行の最適性」のトレードオフを、どこに振るかの調整です。

対策やることコスト向く状況
強制再コンパイル実行のたびに値を見て最適化し直す毎回の最適化CPUを払う稀にしか呼ばれず偏りが激しいクエリ
リテラル埋め込みバインド変数をやめ値を直書きし形を分岐させるプランキャッシュが値ごとに膨張値の種類が少なく各々に専用プランが要る
特定値で最適化代表値を指定してプランを固定する指定外の値は不利になりうる支配的な値が明確に1つある
プラン固定/ヒント良いプランを記録し強制適用する統計が変わっても追従しない良いプランが既知で揺れを止めたい

主要な実装の機構を対応づけると次のとおりです。実行ごとに最適化し直すのは、SQL Server の OPTION(RECOMPILE)、Oracle の動的サンプリングや適応的アプローチに相当します。「特定値で固定」は SQL Server の OPTIMIZE FOR ヒントが典型です。PostgreSQL のサーバー側プリペアドは、最初の数回(既定で5回)はスニッフィングしたカスタムプランを作り、その平均コストと、値を見ない**汎用プラン(generic plan)**のコストを比較して、汎用が十分安ければ以降は汎用へ切り替える、というハイブリッド戦略を取ります(plan_cache_mode で挙動を強制可能)。

まず汎用プランか専用プランかを切り分ける

PostgreSQL なら SET plan_cache_mode = force_generic_plan / force_custom_plan; を切り替えて同じクエリの速度を比べると、スニッフィングが原因かを即座に切り分けられます。force_custom が速ければ値依存が強く、再利用を諦めて毎回最適化する価値があるという判断につながります。

ヒントとプラン固定の落とし穴

プランを固定すれば揺れは止まりますが、固定は「最適化を将来にわたり凍結する」ことでもあります。データ量や分布は時間とともに変わるため、今日最適なプランが半年後には最悪になりえます。プラン固定やヒントは、原因不明の揺れを緊急停止する止血策としては有効でも、恒久対策としては統計の鮮度管理(ANALYZE の徹底や自動統計収集の調整)の代わりにはなりません。根治は、偏りを統計で正しく表現し、必要なクエリだけ再最適化を許す設計です。

試験・面接での問われ方

「プリペアドステートメントは速いのに、ある実行だけ極端に遅いのはなぜか」と問われたら、初回コンパイル時の引数値に最適化されたプランがキャッシュされ、分布の異なる別の値で再利用されるパラメータスニッフィングが原因、と答えます。対策として、毎回の再コンパイル、代表値での固定、汎用/専用プランの切替(PostgreSQL の plan_cache_mode 等)を、再利用率と最適性のトレードオフとして説明できると的確です。

まとめ

プランキャッシュは最適化コストを償却するための再利用機構で、キーは「値ではなく形」です。この設計ゆえに、初回値にスニッフィングしたプランが分布の異なる値へ再利用されると、推定行数と実測行数が乖離し、Nested Loop の反復爆発などで性能が崩壊します。対策はすべて再利用率と最適性のトレードオフ調整であり、強制再コンパイル・リテラル埋め込み・代表値固定・汎用/専用プラン切替を、クエリの呼び出し頻度と値の偏りに応じて使い分けます。EXPLAIN ANALYZE で推定と実測のズレを観測し、ヒントによる凍結ではなく統計の鮮度(→ クエリ最適化)で根治するのが本筋です。

データベース Article

プランキャッシュとパラメータスニッフィングの内部を実務で読む

TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。

解決すること

プランキャッシュ

比較で見る軸

難易度: advanced / カテゴリ: データベース / タグ数: 5

導入後に効く点

パラメータスニッフィングは、初回コンパイル時の引数値に最適化されたプランが、分布の偏った別の値では破滅的に遅くなる現象を指す。

先に潰すリスク

用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。

数字・仕様の読み方
難易度
advanced
カテゴリ
データベース
タグ数
5

判断チェックリスト

  • 自社の用途が「プランキャッシュ / パラメータスニッフィング」に近いか確認する。
  • 強みである「プリペアドステートメントはパース・最適化の結果をキャッシュし、実行のたびにコスト見積もりを省く再利用機構である。」が本当に評価軸になるか確認する。
  • 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
  • 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
  • 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
  • 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。

次に確認する観点

プランキャッシュパラメータスニッフィングプリペアドステートメント実行計画統計情報プランキャッシュパラメータスニッフィングプリペアドステートメント