なぜ「統計情報」でハマるのか? PostgreSQLの隠れた名機能『pg_stats_ext』を使いこなそう
現場でバリバリPostgreSQLを触っていると、一度は経験するよね。「このSQL、インデックスも張ってあるし条件も単純なのに、なんでこんなに遅いの?」っていう現象。
実行計画(EXPLAIN ANALYZE)を見てみると、案の定「見積もり件数(rows)」と「実際の結果件数」が桁違いにズレている。プランナが「これならフルスキャンの方が速いな」と判断して、インデックスを無視した結果、地獄を見るパターンだ。
実はこれ、「列間の相関」が原因のことがめちゃくちゃ多い。今日は、そんな泥沼から抜け出すための切り札、『pg_stats_ext』について話そうと思う。
—
「列の相関」という罠
例えば、ユーザーテーブルで「都道府県」と「市区町村」の列があるとする。
`WHERE 都道府県 = ‘東京都’ AND 市区町村 = ‘千代田区’` というクエリを投げたとき、PostgreSQLのプランナは普通、こう考えるんだ。
1. 東京都である確率:1/47
2. 千代田区である確率:1/2000
3. だから、両方満たす確率は `1/47 × 1/2000` だ!
……とね。でも、現実には「東京都」かつ「千代田区」というデータは存在するけど、「北海道」かつ「千代田区」なんてデータは存在しないよね。プランナはそれぞれの列を「独立している」と思い込んで計算するから、見積もりが盛大に狂うんだ。
ここで登場するのが拡張統計(Extended Statistics)だよ。
—
pg_stats_ext で何が見えるのか?
拡張統計を作成すると、PostgreSQLは単なる列ごとの統計じゃなくて、列の組み合わせについても「こいつら、実はセットで動いてるぞ」という情報を収集してくれるようになる。
で、その作成した統計情報が今どうなっているのか、正しく機能しているのかを確認するためのシステムビューが `pg_stats_ext` だ。
まずは現状を確認してみよう
以下のクエリを叩いてみてほしい。
SELECT
schemaname,
relname,
extname,
extkind, — どんな統計か(依存関係やリストなど)
extcols — どの列を対象にしているか
FROM pg_stats_ext;
ここで `extkind` に注目してほしい。
- `d` (Dependencies): 列間の依存関係(さっきの都道府県・市区町村の例)
- `m` (MCV: Most Common Values): よく出る値の組み合わせ(カテゴリの偏りが激しい場合など)
—
実践:統計情報を育てていく手順
ただビューを見るだけじゃなくて、実際にどう活用するか。手順はシンプルだ。
1. 統計を作る
もし「この組み合わせ、絶対相関してるよな」という列があれば、迷わず作成する。
CREATE STATISTICS stats_pref_city
(dependencies)
ON prefecture, city
FROM users;
2. 統計が有効か確認する
次に `pg_stats_ext_exprs` や `pg_stats_ext` を見て、ちゃんと統計が生成されているか確認する。特に、解析(ANALYZE)が走らないと統計は空っぽのままだから注意してね。
ANALYZE users;
— 統計の内容を覗いてみる
SELECT FROM pg_stats_ext_exprs
WHERE extname = ‘stats_pref_city’;
—
先輩からのアドバイス:使いどころの「勘所」
これ、便利だからといって全列に貼ればいいわけじゃない。統計情報を増やすと、その分 `ANALYZE` のコストも上がるし、管理コストも地味に効いてくる。
僕が現場でよく見る「導入のサイン」はこんな感じだ。
- 見積もりの乖離が10倍以上ある:
`EXPLAIN ANALYZE` で `rows=10000` なのに `actual rows=10` みたいなやつ。これはもう確定で拡張統計の出番。
- WHERE句の列が論理的に結びついている:
「カテゴリ」と「サブカテゴリ」、「状態」と「ステータス」など。
- クエリの実行計画がコロコロ変わって不安定:
統計の精度のせいでプランナが迷っている証拠だ。
まとめ
PostgreSQLのプランナは非常に優秀だけど、データの世界を完璧に理解しているわけじゃない。僕らエンジニアが「このデータにはこういう関係性があるんだよ」とヒントを与えてあげることで、データベースは本来のポテンシャルを発揮してくれる。
`pg_stats_ext` を眺めることは、データベースの「思考のクセ」を理解することに他ならないんだ。
もし次に実行計画で首を傾げることがあったら、ぜひ `pg_stats_ext` を開いてみてくれ。きっとそこに、解決の糸口が隠れているはずだから。
それじゃ、また現場で!
コメント