「プランナが嘘をつく」理由。PostgreSQLのMCVリストを攻略せよ
現場でクエリを書いていて、「明らかにヒット数が少ないはずなのに、どうしてフルスキャンなんてしてるんだ?」と頭を抱えたことはないだろうか。
インデックスは貼ってあるし、`VACUUM ANALYZE`も定期的に回している。それなのに、PostgreSQLのオプティマイザがなぜかヘンテコな実行計画を選んでしまう。そんな時、十中八九犯人は「統計情報の不一致」だ。
今回は、その統計情報の要である「MCVリスト(Most Common Values)」について、実戦的な話をしようと思う。教科書的な定義じゃなく、現場でどう立ち回るべきかという視点でね。
—
MCVリストって結局なに?
一言で言えば、「データベースが覚えている『よく出る値』のカンニングペーパー」だ。
PostgreSQLのプランナは、クエリを実行する前に「この条件で検索したら、だいたい何行くらい返ってくるかな?」と見積もりを立てる。その際、テーブルの全行をスキャンするわけにはいかないから、`pg_statistic`というシステムカタログに保存された統計情報を使うんだ。
その中にあるのがMCVリスト。データ分布が偏っているカラムにおいて、「この値がこれくらいの頻度で出てくる」という情報を上位から順に保持している。
例えば、`status`カラムで「完了(done)」が9割を占めるようなテーブルを想像してほしい。プランナはこのMCVリストを見ることで、「`WHERE status = ‘done’`なら、テーブルの90%を読みに行くから、インデックスよりシーケンシャルスキャンの方が速いな」と判断できるわけだ。
なぜMCVが「罠」になるのか
ここからが本題だ。なぜチューニング中にこのMCVが問題になるのか。
一番多いパターンは、「データの偏りが激しすぎて、MCVの容量を超えてしまう」ことや、「データの更新頻度に対して統計情報の更新が追いついていない」ことだ。
特に、`WHERE`句で「滅多に出ない値」を指定したとき、プランナがMCVを参考に「これもよく出る値(MCVに含まれる値)と同じくらいヒットするはずだ」と誤解してしまい、インデックスを使わずに重い処理を選択してしまうことがよくある。
実践:統計情報を覗いてみよう
まずは自分の環境で、特定のカラムのMCVがどうなっているか確認してみよう。`pg_stats`というビューを使うのが一番手っ取り早い。
SELECT
attname AS column_name,
n_distinct,
most_common_vals,
most_common_freqs
FROM pg_stats
WHERE tablename = ‘your_table_name’
AND attname = ‘your_column_name’;
`most_common_vals`に並んでいるのがMCV、`most_common_freqs`がそれぞれの出現頻度だ。
もし、ここを見ても「今まさに遅いクエリで指定している値」が含まれていないなら、プランナは「その他大勢(Histogram)」として一律の計算をしてしまっている。これが推論のズレを生む原因だ。
解決策:どうやってチューニングするか
もし統計情報のズレが原因なら、以下のステップを試してみてほしい。
1. まずは `ANALYZE` の精度を上げる
デフォルトの`default_statistics_target`(通常100)が低すぎて、MCVが十分に収集できていないケースがある。特定のカラムだけ精度を上げれば、より詳細な統計が取れるようになる。
— 特定のカラムだけ統計情報の収集精度を上げる(1000くらいまで上げるとかなり詳細になる)
ALTER TABLE your_table ALTER COLUMN your_column SET STATISTICS 1000;
ANALYZE your_table;
これだけで実行計画が劇的に改善することは珍しくない。
2. それでもダメなら「相関」を疑う
MCVはあくまで「一つのカラム」の統計だ。もし複数のカラムを組み合わせた検索で遅いなら、`CREATE STATISTICS`を使って、カラム間の相関関係をプランナに教えてやる必要がある。
— 複数カラムの相関関係を統計に含める
CREATE STATISTICS stats_name (dependencies) ON col1, col2 FROM your_table;
ANALYZE your_table;
—
先輩からのアドバイス
最後に一つだけ覚えておいてほしい。「統計情報をいじりすぎないこと」だ。
`SET STATISTICS`を上げれば精度は上がるが、その分`ANALYZE`の負荷は高くなるし、システムカタログも肥大化する。闇雲に全部のカラムで精度を上げればいいというものじゃない。
本当に遅いクエリを見つけたら、まずは`EXPLAIN ANALYZE`で「実際の行数(actual rows)」と「見積もり行数(rows)」の乖離を確認する。そこが大きくズレている箇所だけに絞って手当てをする。これが、熟練のエンジニアがやっている「狙い撃ちのチューニング」だ。
データベースは嘘をつかない。嘘をついているように見える時は、こちらが「正しい情報」を与えられていないだけなんだ。
さあ、次は君の環境の`pg_stats`を覗いてみるところから始めてみよう。何か発見があったら、また聞かせてくれよな。
コメント