PostgreSQL 统计信息 --- 完整总结与优化指南
一、统计信息体系全览
统计信息
├── 关系级(pg_class)
│ ├── reltuples --- 行数估算
│ ├── relpages --- 页数
│ └── relallvisible --- 可见页数(用于 Index Only Scan 成本估算)
│
├── 列级(pg_statistic / pg_stats)
│ ├── null_frac --- 空值比例
│ ├── n_distinct --- 非重复值数量/比例
│ ├── most_common_vals / most_common_freqs --- 高频值(MCV)列表
│ ├── histogram_bounds --- 等频直方图边界
│ ├── avg_width --- 平均列宽
│ └── correlation --- 物理/逻辑顺序相关性
│
└── 扩展统计信息(pg_statistic_ext / pg_statistic_ext_data)
├── 表达式统计信息(expression)
├── 函数依赖(dependencies)
├── 多元非重复值(ndistinct)
└── 多元 MCV 列表(mcv)
二、各类统计信息详解
2.1 关系级基础统计(pg_class)
| 字段 | 说明 | 更新时机 |
|---|---|---|
reltuples |
估算行数,-1 表示从未分析 |
ANALYZE / VACUUM / CREATE INDEX |
relpages |
表占用页数 | 同上 |
relallvisible |
可见性映射中标记的页数 | VACUUM |
行数缩放机制 :规划器发现 relpages 与实际文件大小不符时,会按比例缩放 reltuples,无需等待下次 ANALYZE:
sql
-- 规划器内部等效计算
SELECT reltuples * (pg_relation_size('t') / 8192) / relpages FROM pg_class WHERE relname = 't';
未分析的新表,规划器默认假设表有 10 页、410 行。
2.2 空值比例(null_frac)
用于估算 IS NULL / IS NOT NULL 条件的选择率:
预估行数 = reltuples × null_frac -- IS NULL
预估行数 = reltuples × (1 - null_frac) -- IS NOT NULL
2.3 非重复值(n_distinct)
| 值 | 含义 |
|---|---|
正数(如 104) |
列中非重复值的绝对数量 |
负数(如 -1) |
非重复值占总行数的比例(-1 = 全部唯一,-0.5 = 每值平均出现 2 次) |
当非重复值数量超过总行数 10% 时,分析器自动改用比例表示。
用于估算 col = $param(运行时才知道值)的选择率:
选择率 = 1 / n_distinct
手动修正偏差:
sql
ALTER TABLE t ALTER COLUMN c SET (n_distinct = 200);
-- 或使用比例
ALTER TABLE t ALTER COLUMN c SET (n_distinct = -0.1);
2.4 高频值列表(MCV)
most_common_vals + most_common_freqs 两个数组一一对应,存储出现频率最高的值及其频率。
用于估算:
col = 常量:直接查 MCV 频率col < 常量/col > 常量:累加满足条件的 MCV 频率,再结合直方图
sql
-- 查看某列 MCV
SELECT most_common_vals, most_common_freqs
FROM pg_stats
WHERE tablename = 'flights' AND attname = 'aircraft_code';
MCV 数组最大长度由 default_statistics_target 控制(默认 100)。
2.5 等频直方图(histogram_bounds)
当非重复值过多无法全部放入 MCV 时,使用直方图补充。
- 桶数量 =
default_statistics_target(默认 100) - 等频:每个桶包含大致相同数量的值
- MCV 中的值不计入直方图
- 空值不计入直方图
选择率估算(以 col > X 为例):
选择率 = MCV中满足条件的频率之和
+ (1 - 所有MCV频率之和 - null_frac) × (满足条件的桶数 / 总桶数)
桶边界之间的值使用线性插值。
2.6 平均列宽(avg_width)
单位:字节。用于估算排序、哈希等操作所需内存。
sql
SELECT attname, avg_width FROM pg_stats WHERE tablename = 'tickets';
2.7 相关性(correlation)
物理存储顺序与逻辑排序顺序的相关系数,范围 [-1, 1]:
| 值 | 含义 |
|---|---|
接近 1 |
数据按升序物理存储,索引扫描回表代价低 |
接近 -1 |
数据按降序物理存储 |
接近 0 |
数据随机分布,索引扫描回表代价高(倾向全表扫描) |
相关性越低,Bitmap Index Scan 相比 Index Scan 越有优势(批量回表减少随机 I/O)。
三、扩展统计信息
3.1 表达式统计信息
问题 :列上有函数运算时(如 extract(month FROM col) = 1),规划器无法使用列统计,默认选择率固定为 0.5%,严重低估。
方案一:扩展表达式统计(PG 14+,无需索引)
sql
CREATE STATISTICS flights_expr ON (
extract(month FROM scheduled_departure AT TIME ZONE 'Europe/Moscow')
) FROM flights;
ANALYZE flights;
方案二:表达式索引(同时获得索引和统计信息)
sql
CREATE INDEX ON flights(
extract(month FROM scheduled_departure AT TIME ZONE 'Europe/Moscow')
);
ANALYZE flights;
查询中的表达式写法必须与
CREATE STATISTICS/CREATE INDEX时完全一致,统计信息才会被使用。
查看表达式统计:
sql
SELECT expr, null_frac, n_distinct, most_common_vals, correlation
FROM pg_stats_ext_exprs
WHERE statistics_name = 'flights_expr';
3.2 函数依赖(dependencies)
问题 :多列过滤时,规划器假设谓词独立,将各列选择率相乘,导致严重低估(相关列问题)。
典型场景 :flight_no 决定 departure_airport,两列同时过滤时估算偏低。
sql
CREATE STATISTICS flights_dep(dependencies)
ON flight_no, departure_airport FROM flights;
ANALYZE flights;
查看依赖程度(0=无依赖,1=完全依赖):
sql
SELECT dependencies
FROM pg_stats_ext WHERE statistics_name = 'flights_dep';
-- {"2 => 5": 1.000000, "5 => 2": 0.010200}
3.3 多元非重复值(ndistinct)
问题 :GROUP BY col1, col2 时,规划器将两列非重复值数量相乘,严重高估组合数。
sql
CREATE STATISTICS flights_nd(ndistinct)
ON departure_airport, arrival_airport FROM flights;
ANALYZE flights;
查看:
sql
SELECT n_distinct FROM pg_stats_ext WHERE statistics_name = 'flights_nd';
-- {"5, 6": 618}
3.4 多元 MCV 列表(mcv)
问题:函数依赖统计对分布不均的数据效果有限,特定值组合的频率估算仍不准确。
sql
CREATE STATISTICS flights_mcv(mcv)
ON departure_airport, aircraft_code FROM flights;
ANALYZE flights;
查看特定组合频率:
sql
SELECT values, frequency
FROM pg_statistic_ext stx
JOIN pg_statistic_ext_data stxd ON stx.oid = stxd.stxoid,
pg_mcv_list_items(stxd.stxdmcv) m
WHERE stxname = 'flights_mcv'
AND values = '{SVO,733}';
3.5 组合多种扩展统计
sql
-- 同时收集函数依赖 + 多元 MCV
CREATE STATISTICS flights_combined(dependencies, mcv)
ON col1, col2 FROM t;
-- 不指定类型 = 收集所有类型
CREATE STATISTICS flights_all ON col1, col2, col3 FROM t;
四、统计信息收集机制
采样策略
- 采样行数 =
300 × default_statistics_target(默认 100,即采样 30000 行) - 从随机页面中采样,与表总大小关系不大
- 小表可能采样全部数据;大表为抽样,存在误差(属正常现象)
触发时机
| 操作 | 更新内容 |
|---|---|
ANALYZE |
列级统计 + reltuples/relpages |
VACUUM |
relallvisible(可见页数) |
VACUUM FULL / CLUSTER |
reltuples/relpages |
CREATE INDEX / REINDEX |
reltuples/relpages |
autovacuum(自动分析) |
同 ANALYZE |
五、常用查询 SQL
sql
-- 查看表基础统计
SELECT reltuples, relpages, relallvisible
FROM pg_class WHERE relname = 'your_table';
-- 查看列级统计概览
SELECT attname, null_frac, n_distinct, avg_width, correlation
FROM pg_stats
WHERE tablename = 'your_table'
ORDER BY attname;
-- 查看 MCV 列表
SELECT attname, most_common_vals, most_common_freqs
FROM pg_stats
WHERE tablename = 'your_table' AND attname = 'your_col';
-- 查看直方图
SELECT attname, histogram_bounds
FROM pg_stats
WHERE tablename = 'your_table' AND attname = 'your_col';
-- 查看所有扩展统计信息
SELECT stxname, stxkeys, stxkind
FROM pg_statistic_ext
WHERE stxrelid = 'your_table'::regclass;
-- 查看统计信息最后更新时间
SELECT relname, last_analyze, last_autoanalyze, n_live_tup, n_dead_tup
FROM pg_stat_user_tables
WHERE relname = 'your_table';
六、关键参数
| 参数 | 默认值 | 说明 |
|---|---|---|
default_statistics_target |
100 | 全局采样精度,影响 MCV 数组大小和直方图桶数 |
列级覆盖(优先级高于全局参数):
sql
-- 提高特定列的统计精度(采样量同步增加)
ALTER TABLE t ALTER COLUMN c SET STATISTICS 500;
-- 恢复默认
ALTER TABLE t ALTER COLUMN c SET STATISTICS -1;
-- 调整扩展统计信息大小
ALTER STATISTICS stat_name SET STATISTICS 200;
-- 调整表达式索引统计精度
ALTER INDEX idx_name ALTER COLUMN col_name SET STATISTICS 200;
七、问题诊断与优化决策树
EXPLAIN ANALYZE 中 rows 估算偏差 > 10 倍?
│
├─ 是单列过滤?
│ ├─ 列上有函数运算? → 创建表达式统计 或 表达式索引
│ ├─ 高频值分布不均? → 提高该列 STATISTICS 值
│ └─ 统计信息过时? → 手动 ANALYZE
│
└─ 是多列过滤 / GROUP BY?
├─ 列之间存在函数依赖? → CREATE STATISTICS ... (dependencies)
├─ GROUP BY 组合数估算偏高? → CREATE STATISTICS ... (ndistinct)
└─ 特定值组合频率不准? → CREATE STATISTICS ... (mcv)
八、优化建议总结
-
保持 autovacuum 正常运行:这是统计信息自动更新的基础,禁用会导致统计信息持续老化。
-
大批量写入后手动 ANALYZE :
INSERT/UPDATE/DELETE大量数据后,不要等 autovacuum 触发:sqlANALYZE your_table; -
对高选择性列提高采样精度:用于 JOIN 条件、WHERE 过滤的关键列,适当提高 STATISTICS 值(200~500)。
-
对表达式过滤创建统计或索引:避免规划器使用固定 0.5% 的默认选择率。
-
多列相关性问题优先用多元统计:比修改查询写法侵入性更小,且对应用透明。
-
不要盲目调大
default_statistics_target:全局调大会增加所有表的 ANALYZE 时间和规划时间,应按需在列级别调整。 -
n_distinct估算偏差时手动修正:对于采样不足导致非重复值数量估算不准的列,直接设置:sqlALTER TABLE t ALTER COLUMN c SET (n_distinct = 500); -
相关性低的列避免依赖 Index Scan :
correlation接近 0 时,规划器倾向于 Bitmap Scan 或 Seq Scan,这通常是正确的,不要强制开启 Index Scan。