PostgreSQL の実行計画はプランナが統計情報をもとに選択します。統計が古ければ、インデックスよりシーケンシャルスキャンの方が速いと誤判断し、遅いクエリが量産されます。
この記事では、統計情報を更新する
ANALYZE コマンドの基本から、精度を制御する default_statistics_target の調整方法まで解説します。RHEL 9.4 / Ubuntu 24.04 LTS + PostgreSQL 16 で動作確認済みです。この記事のポイント
・ANALYZE でプランナ統計を更新すればプラン選択ミスを直せる
・EXPLAIN ANALYZE の推定行数と実測行数のずれが診断の入口
・default_statistics_target を上げると統計精度が向上する
・列ごとに統計精度を個別調整する ALTER TABLE SET STATISTICS も有効
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
なぜ実行計画がずれるのか? — プランナ統計の仕組み
PostgreSQL のクエリプランナは、テーブルをフルスキャンするか、インデックスを使うかを「コストベース」で判断します。そのコスト計算の根拠が プランナ統計 です。統計は
pg_statistic システムカタログ(一般ユーザーは pg_stats ビューで参照)に保存されており、以下の情報を含んでいます。・null_frac:NULL 値の割合
・n_distinct:一意な値の推定数
・most_common_vals:出現頻度の高い値とその頻度
・histogram_bounds:値の分布(ヒストグラム)
大量の INSERT/UPDATE/DELETE を実行すると、実際のデータ分布と統計の値がかけ離れます。その結果、プランナは「このテーブルには 100 行しかない」と思っているのに実際は 100 万行、といった判断ミスを起こします。
ANALYZE を実行すると、PostgreSQL はテーブルからサンプルをランダムに抽出し、統計を更新します。これだけで実行計画が劇的に改善するケースは珍しくありません。
EXPLAIN ANALYZE で診断する — 推定行数と実測行数のずれを見る
統計が古いかどうかはEXPLAIN ANALYZE で確認します。-- 実行計画と実際の行数を同時に取得 EXPLAIN ANALYZE SELECT * FROM orders WHERE customer_id = 12345;
Seq Scan on orders (cost=0.00..45230.00 rows=1 width=128) (actual time=0.042..312.451 rows=89234 loops=1) Filter: (customer_id = 12345) Rows Removed by Filter: 910766 Planning Time: 0.085 ms Execution Time: 337.201 ms
rows=1(推定)と rows=89234(実測)が大きくかけ離れています。プランナは「1 行しか返らない」と思っているためインデックスを使わずシーケンシャルスキャンを選んでいます。この状態が統計の腐敗です。ANALYZE の基本的な使い方
1. データベース全体の統計情報を更新する
-- psql でデータベースに接続した状態で実行 ANALYZE; -- または VERBOSE で更新されたテーブルを確認 ANALYZE VERBOSE;
ANALYZE のみで接続中のデータベース全テーブルの統計を更新します。VERBOSE を付けると処理ログが表示されます。INFO: analyzing "public.orders" INFO: "orders": scanned 30000 of 110453 pages, containing 2000000 live rows and 0 dead rows; 30000 rows in sample, 7364044 estimated total rows ANALYZE
2. 特定テーブルだけ更新する
テーブルを絞ることで処理時間を短縮できます。大量更新後の対象テーブルだけ即時更新するときに有効です。# 特定テーブルのみ ANALYZE orders; # 特定テーブルの特定列だけ ANALYZE orders (customer_id, order_date); # スキーマを指定する場合 ANALYZE sales.orders;
3. ANALYZE 後に実行計画を再確認する
ANALYZE orders; EXPLAIN ANALYZE SELECT * FROM orders WHERE customer_id = 12345;
Index Scan using idx_orders_customer_id on orders (cost=0.56..8432.12 rows=87510 width=128) (actual time=0.021..18.432 rows=89234 loops=1) Index Cond: (customer_id = 12345) Planning Time: 0.210 ms Execution Time: 22.840 ms
87510、実測が 89234 と近づき、インデックスが選ばれました。実行時間も 337ms から 22ms に改善しています。default_statistics_target でサンプル精度を上げる
1. default_statistics_target の役割とデフォルト値
ANALYZE が収集するサンプル数はdefault_statistics_target で制御します。デフォルトは 100 で、300 × default_statistics_target 行をサンプリングします。・デフォルト(100):サンプル 30,000 行。一般的なテーブルには十分
・値を大きくする:サンプル数が増え統計が精密になる。ANALYZE の時間も増加する
・値を小さくする:サンプル数が減り ANALYZE が速くなるが精度が落ちる
現在の値は次のクエリで確認します。
SHOW default_statistics_target; default_statistics_target --------------------------- 100 (1 row)
2. postgresql.conf でデータベース全体のデフォルトを変更する
# /etc/postgresql/16/main/postgresql.conf (Ubuntu系) # または /var/lib/pgsql/16/data/postgresql.conf (RHEL系) default_statistics_target = 200
SIGHUP で反映します。再起動は不要です。# postgresql.conf の変更を再起動なしで反映 SELECT pg_reload_conf(); pg_reload_conf ---------------- t (1 row)
ANALYZE VERBOSE;
3. 列ごとに統計精度を個別調整する
全体のデフォルトを上げると ANALYZE 時間が全テーブルで増加します。特定の列だけ精度を上げたい場合はALTER TABLE ... ALTER COLUMN ... SET STATISTICS を使います。# customer_id 列だけ統計精度を 500 に引き上げる ALTER TABLE orders ALTER COLUMN customer_id SET STATISTICS 500; # 変更を反映するために ANALYZE を再実行 ANALYZE orders;
-1 を指定するとデータベースのデフォルト値に戻ります。# デフォルトに戻す ALTER TABLE orders ALTER COLUMN customer_id SET STATISTICS -1;
pg_stats の n_distinct が実際の値と大きくずれている列を優先して上げるのが実務上の鉄則です。-- 統計の n_distinct を確認する SELECT attname, n_distinct, null_frac, array_length(most_common_vals::text::text[], 1) AS mcv_count FROM pg_stats WHERE tablename = 'orders' ORDER BY attname;
autovacuum による自動 ANALYZE — 運用上の注意点
PostgreSQL は autovacuum デーモンが一定の更新量を超えると自動的に ANALYZE を実行します。デフォルトの閾値は「テーブル行数の 20% + 50 行」の更新が発生したとき。・自動 ANALYZE が追いつかないケース:バッチ処理で短時間に大量 INSERT したとき
・対応方法:バッチ後に手動で
ANALYZE テーブル名; を実行するか、cron で定期実行する・自動 ANALYZE の最終実行時刻確認:
SELECT schemaname, relname AS tablename, last_autoanalyze, last_analyze, n_live_tup, n_dead_tup FROM pg_stat_user_tables ORDER BY last_autoanalyze NULLS FIRST;
schemaname | tablename | last_autoanalyze | last_analyze | n_live_tup | n_dead_tup ------------+-----------+----------------------------+----------------------------+------------+------------ public | orders | 2026-09-09 03:15:42.123456 | 2026-09-10 09:30:12.654321 | 1000000 | 2344 public | products | 2026-09-10 00:05:11.000000 | | 50000 | 120 (2 rows)
last_autoanalyze が NULL のテーブルや古い日付のテーブルは、手動 ANALYZE の候補です。トラブルシュート — ANALYZE しても実行計画が改善しない場合
1. enable_seqscan が off になっていないか確認するSHOW enable_seqscan; enable_seqscan ---------------- off (1 row)
enable_seqscan = off に設定されているとシーケンシャルスキャンが強制的に無効化されます。デバッグのために一時的に設定されていることがあるため、確認して on に戻します。SET enable_seqscan = on;
PostgreSQL の通常統計は列単独の分布しか保持しません。
WHERE region = 'east' AND category = 'food' のような複数列の組み合わせに対しては、CREATE STATISTICS(拡張統計)が有効です。-- region と category の組み合わせの相関統計を収集する CREATE STATISTICS ext_region_category ON region, category FROM orders; ANALYZE orders;
ANALYZE はテーブルオーナーまたはスーパーユーザーのみ実行可能です。一般ユーザーが実行すると次のエラーが出ます。ERROR: must be owner of table orders
postgres ユーザーで実行するか、テーブルオーナーのロールで実行します。まとめ
| やりたいこと | コマンド / 設定 |
|---|---|
| 全テーブルの統計を更新する | ANALYZE; |
| 特定テーブルのみ更新する | ANALYZE テーブル名; |
| 実行計画と実測を同時確認する | EXPLAIN ANALYZE SELECT ...; |
| 統計サンプル精度をDB全体で上げる | postgresql.conf の default_statistics_target = 200 |
| 特定列だけ統計精度を上げる | ALTER TABLE t ALTER COLUMN col SET STATISTICS 500; |
| autovacuum の最終 ANALYZE を確認する | SELECT last_autoanalyze FROM pg_stat_user_tables; |
| 複数列の相関統計を収集する | CREATE STATISTICS ... ON col1, col2 FROM t; |
EXPLAIN ANALYZE で推定行数と実測行数のギャップを確認し、ANALYZE を実行してみてください。それでも改善しない場合は default_statistics_target の引き上げ、次いで拡張統計という順で対処します。「PostgreSQL をもっと本番運用で使えるようにしたい」「クエリチューニングや障害対応まで自信を持ちたい」という方は、20 年以上の実務経験をベースにした Linux/DB 運用スキルを体系的に身につけられる Linux Master Pro Seminar もご覧ください。
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら
- 前のページへ:PostgreSQLとMySQLの違いを自前運用の視点で比較する|アーキテクチャとレプリケーション方式・向き不向きの見極め
- この記事の属するカテゴリ:データーベース管理へ戻る

無料メルマガで学習を続ける
Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。
登録無料・いつでも解除できます