【実務・中級編】 一意な値の数(n_distinct) – PostgreSQL

「クエリがなぜか遅い……」
そんなとき、`EXPLAIN ANALYZE`を叩いてみて、実際の実行計画と推定コストが大きく乖離しているのを見たことはありませんか?

「いや、オプティマイザさん、そこは数十万行じゃなくて数千行でしょ!」と画面越しにツッコミを入れたくなる瞬間。実はその原因の多くが、`n_distinct`、つまり「その列にどれだけユニークな値があるか」の統計情報がズレていることにあります。

今日は、PostgreSQLのパフォーマンスチューニングにおいて、地味だけどめちゃくちゃ重要なこのパラメータについて、現場の知見を共有しようと思います。

—

そもそも `n_distinct` って何者?

PostgreSQLのクエリオプティマイザは、クエリを実行する前に「どうやってデータを処理するのが一番速いか」を計算します。その際、「この列には何種類のユニークな値があるか?」という情報が不可欠です。

例えば、`status`カラムに `active` と `inactive` の2種類しかないテーブルと、`user_id`のように全行ユニークな値が入っているテーブルでは、結合(JOIN)やグルーピング(GROUP BY)の戦略が全く変わりますよね?

この「ユニーク値の数」を保持しているのが `pg_stats` ビューの `n_distinct` 列です。

統計情報の見方

まずは、自分のテーブルで統計がどう認識されているか確認してみましょう。

SELECT attname, n_distinct, most_common_vals
FROM pg_stats
WHERE tablename = ‘your_table_name’;

ここで `n_distinct` が `-1` になっている場合、「全行ユニーク(100%ユニーク)」であることを意味します。逆に正の数であれば「その数だけユニークな値がある」と推定されています。

—

よくある「統計情報の罠」

僕が現場でよく見る「ハマりポイント」はこれです。

1. 急激なデータ変動に追いつかない

バッチ処理で数百万行を一気に投入した直後、`ANALYZE` が走る前にクエリを叩くと、オプティマイザは古い統計情報をもとに「このテーブルは空に近い」と判断し、無謀なネステッドループを選択して爆死します。

2. 複合条件による「推定の限界」

PostgreSQLは基本的に列単体の統計しか見ません。例えば「都道府県」と「市区町村」の相関関係までは考慮できないことが多い。結果として、絞り込み後の行数を極端に過小評価してしまい、実行計画が崩れることがあります。

—

現場で使えるチューニング術

もし `n_distinct` のせいで実行計画が狂っていると確信したなら、以下の方法を試してみてください。

A. まずは基本の ANALYZE

基本中の基本ですが、テーブルの統計情報を手動で更新します。

ANALYZE VERBOSE your_table_name;

B. 統計情報の解像度を上げる

デフォルトの統計取得数(`default_statistics_target`)では足りない場合があります。特定の列の推定精度が悪いなら、その列だけサンプリング精度を上げてみましょう。

— 対象の列だけ統計ターゲットを増やす(デフォルトは100)
ALTER TABLE your_table_name ALTER COLUMN your_column SET STATISTICS 500;
ANALYZE your_table_name;

これだけで、オプティマイザが持つヒストグラムの解像度が上がり、劇的に計画が改善することがあります。

C. それでもダメなら「拡張統計」

もし「列Aと列Bを組み合わせたときのユニーク数」が重要なら、PostgreSQL 10から導入された拡張統計(Extended Statistics)の出番です。

CREATE STATISTICS stats_col_a_b (dependencies) ON col_a, col_b FROM your_table_name;
ANALYZE your_table_name;

これを設定すると、PostgreSQLは「この2つの列には相関関係がある」ことを学習し、より精度の高いコスト見積もりをしてくれるようになります。

—

最後に:完璧を求めすぎないこと

ここまで解説してきましたが、最後に一つだけ。統計情報はあくまで「推定」です。

たまに「統計を完璧に合わせれば100%速くなる」と勘違いして、全ての列の統計ターゲットを最大値にする人がいますが、これはNGです。統計情報の作成自体が重い処理になりますし、過学習ならぬ「過剰な統計」はメンテナンスコストを跳ね上げます。

基本は「デフォルトの統計でうまくいくように設計する(インデックスやデータ分布を整える)」こと。その上で、どうにもならない「急所」だけを、今回紹介した手法でピンポイントにケアする。

このバランス感覚こそが、現場で信頼されるエンジニアの技術力だと僕は思っています。

皆さんのクエリが、明日から少しでも軽快に動きますように。また何か詰まったら聞きに来てくださいね!

コメント

タイトルとURLをコピーしました