なぜPostgreSQLのプランナは「嘘」をつくのか?:拡張統計情報(CREATE STATISTICS)の活用術
現場でバリバリとクエリを書いていると、たまに「いや、どう考えてもこのクエリは一瞬で終わるはずなのに、なんでこんなに時間がかかるんだ?」と頭を抱える瞬間、ありますよね。
実行計画(`EXPLAIN ANALYZE`)を見てみると、プランナが「ここは数行しか返ってこないはずだから、Nested Loopでいこう!」と判断しているのに、実際には数万行が返ってきてHash Joinに切り替わった結果、システム全体が悲鳴を上げている……なんていう悲劇。
これ、実はPostgreSQLが「列同士の相関関係」を理解できていないことが原因かもしれません。今日は、そんな「プランナの勘違い」を正し、パフォーマンスを劇的に改善する『拡張統計情報(CREATE STATISTICS)』について、現場の知見を共有したいと思います。
—
なぜプランナは読みを外すのか
PostgreSQLの標準的な統計情報は、基本的に「各列の単独の分布」を見ています。
例えば、`users`テーブルに `prefecture`(都道府県)と `city`(市区町村)という2つの列があるとしましょう。「東京都」という値の出現頻度と、「新宿区」という値の出現頻度は個別に把握できています。
しかし、プランナは「東京都を選択したとき、新宿区が選ばれる確率は極めて高い(相関がある)」という関係性までは知りません。
そのため、PostgreSQLは単純に確率を掛け算してしまいます。
「都道府県が東京都である確率(仮に10%) × 市区町村が新宿区である確率(仮に1%) = 0.1%」
……と見積もるわけです。実際には30%くらいあるかもしれないのに、これではプランナは「あ、この条件なら数行しか返ってこないな!」と楽観的な計画を立ててしまいます。これが「見積もりの乖離」の正体です。
救世主:CREATE STATISTICSの出番
この「勘違い」を正すのが `CREATE STATISTICS` です。こいつを使うと、特定の列の組み合わせについて「ここの列たちはセットで考えると、こういう分布になっているよ」という情報をプランナに教え込むことができます。
1. 関数依存関係(Functional Dependencies)
「ある列が決まれば、別の列の値も自動的に決まる」という関係(例:郵便番号が決まれば、都道府県も決まる)がある場合に有効です。
CREATE STATISTICS stats_location_dep
(dependencies)
ON prefecture, city FROM users;
これを作成したあと、`ANALYZE` を実行すると、プランナは「あ、この2つはセットで見ないといけないのか」と理解し、見積もりの精度が劇的に向上します。
2. 多変量N個体数(NDistinct)
「2つの列を組み合わせたとき、ユニークな値が何通りあるか」を推定します。これは、`GROUP BY` を複数列で行う場合や、`DISTINCT` を組み合わせるクエリで威力を発揮します。
CREATE STATISTICS stats_user_groups
(ndistinct)
ON prefecture, city FROM users;
これがあるだけで、グループ化のコスト見積もりが正確になり、適切なメモリ割り当てが行われるようになります。
—
実務で使う際の「心得」
「じゃあ、全部の列に統計情報を貼れば最強じゃん!」と思うかもしれませんが、それは罠です。
- オーバーヘッドに注意: `CREATE STATISTICS` を作成すると、その分 `ANALYZE` の負荷が増えます。テーブルが巨大で更新頻度が高い場合、統計情報の更新がボトルネックになることもあります。
- 本当に困っているところにだけ使う: 実行計画を見て、明らかに「見積もり行数」と「実際行数」が桁違いにズレているクエリを見つけてください。そのボトルネックに対してピンポイントで適用するのが、プロの仕事です。
- ANALYZEを忘れずに: 統計情報を作っただけでは反映されません。必ず `ANALYZE テーブル名;` を実行して、データを再スキャンさせてください。
最後に
データベースエンジニアの仕事は、SQLを書くだけではありません。PostgreSQLという「賢いけれど、時々空回りする部下」に対して、いかに正確な判断材料を与えて、気持ちよく働いてもらうか。そのための調律こそが、チューニングの醍醐味です。
もし今度、不可解な実行計画に出くわしたら、ぜひ `CREATE STATISTICS` を思い出してください。きっと、あなたのクエリを救ってくれるはずです。
それでは、良いチューニングライフを!
コメント