这是《PostgreSQL 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 优化器依赖统计信息估算行数。统计缺失、过期或表达式复杂,都可能导致错误计划。
11.1 ANALYZE
ANALYZE orders;
ANALYZE orders (user_id, status);
查看:
SELECT
relname,
n_live_tup,
n_mod_since_analyze,
last_analyze,
last_autoanalyze
FROM pg_stat_user_tables
ORDER BY n_mod_since_analyze DESC;
autovacuum 会自动触发 analyze,但大表、突变负载和复杂查询仍可能需要手动窗口。
11.2 pg_stats
SELECT
tablename,
attname,
n_distinct,
null_frac,
correlation
FROM pg_stats
WHERE schemaname = 'public'
AND tablename = 'orders';
关键列:
| 列 | 含义 |
|---|---|
| n_distinct | 唯一值估算 |
| null_frac | NULL 比例 |
| correlation | 物理顺序相关性 |
| most_common_vals | 高频值 |
| most_common_freqs | 高频值频率 |
| histogram_bounds | 分布直方图 |
11.3 扩展统计
列相关时,单独统计会低估组合基数:
SELECT *
FROM orders
WHERE city = 'Shanghai' AND platform = 'App';
创建:
CREATE STATISTICS stat_orders_city_platform (dependencies)
ON city, platform FROM orders;
ANALYZE orders;
查看:
SELECT stxname, stxdependencies
FROM pg_statistic_ext;
11.4 统计目标
ALTER TABLE orders ALTER COLUMN status SET STATISTICS 500;
ANALYZE orders;
全局默认:
SHOW default_statistics_target;
目标越高采样越多、统计更细,但 analyze 成本和计划时间增加。热点复杂列可单独提高。
11.5 表达式统计
PostgreSQL 可对表达式收集统计。高频表达式查询可验证:
SELECT *
FROM pg_stats
WHERE tablename LIKE '%lower(email)%';
需要表达式索引或相应统计支持,否则复杂函数过滤可能严重低估或高估。
11.6 统计失效场景
| 场景 | 后果 |
|---|---|
| 大批量导入后未 analyze | 估算过期 |
| JSONB 内部字段 | 默认统计不足 |
| 跨列相关 | 低估组合过滤 |
| 自定义函数 | 难以估算 |
| 分区裁剪条件复杂 | 计划不稳定 |
| 数据倾斜 | 平均分布失真 |
11.7 分区表统计
分区表既要看父表,也要看子分区。新分区必须在数据加载后收集统计,否则首查计划可能异常。
ANALYZE events_2026_08;
11.8 估算问题排查
EXPLAIN ANALYZE
SELECT *
FROM orders
WHERE status = 'PAID'
AND created_at >= now() - interval '1 day';
对比:
plan rows
actual rows
Rows Removed by Filter
loops
处理顺序:
- ANALYZE;
- 查 pg_stats;
- 检查表达式;
- 增加统计目标;
- 增加扩展统计;
- 改写 SQL。
本章小结
统计信息是优化器决策依据。日常要监控 n_mod_since_analyze,大表导入后及时分析,复杂列使用扩展统计和更高统计目标。估算修复往往比强行加索引更有效。
思考题
- ANALYZE 和 VACUUM 的职责有什么不同?
- correlation 会影响哪种扫描?
- 为什么跨列相关需要扩展统计?
- default_statistics_target 是否越大越好?
- 分区表统计要注意什么?