PostgreSQL 统计信息 — 完整总结与优化指南

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)

八、优化建议总结

  1. 保持 autovacuum 正常运行:这是统计信息自动更新的基础,禁用会导致统计信息持续老化。

  2. 大批量写入后手动 ANALYZEINSERT/UPDATE/DELETE 大量数据后,不要等 autovacuum 触发:

    sql 复制代码
    ANALYZE your_table;
  3. 对高选择性列提高采样精度:用于 JOIN 条件、WHERE 过滤的关键列,适当提高 STATISTICS 值(200~500)。

  4. 对表达式过滤创建统计或索引:避免规划器使用固定 0.5% 的默认选择率。

  5. 多列相关性问题优先用多元统计:比修改查询写法侵入性更小,且对应用透明。

  6. 不要盲目调大 default_statistics_target:全局调大会增加所有表的 ANALYZE 时间和规划时间,应按需在列级别调整。

  7. n_distinct 估算偏差时手动修正:对于采样不足导致非重复值数量估算不准的列,直接设置:

    sql 复制代码
    ALTER TABLE t ALTER COLUMN c SET (n_distinct = 500);
  8. 相关性低的列避免依赖 Index Scancorrelation 接近 0 时,规划器倾向于 Bitmap Scan 或 Seq Scan,这通常是正确的,不要强制开启 Index Scan。

相关推荐
有想法的py工程师1 小时前
PostgreSQL vs PolarDB:Checkpoint 调优策略深度对比(高频 vs 低频)
大数据·数据库·postgresql
m0_377618231 小时前
golang如何使用struct嵌套_golang struct结构体嵌套使用方法.txt
jvm·数据库·python
2301_815279521 小时前
Redis如何降低快照对CPU的影响_合理分配RDB执行时机避开业务高峰期
jvm·数据库·python
Greyson12 小时前
Go语言怎么用GitHub Actions_Go语言GitHub Actions教程【基础】.txt
jvm·数据库·python
qq_342295822 小时前
CSS如何实现单选按钮自定义样式_利用伪元素隐藏默认UI
jvm·数据库·python
深念Y2 小时前
状态缓存与TTL:给每个设备状态贴一张“保质期”
数据库·缓存·智能家居·时间·时间戳·智能电视·ttl
m0_640309302 小时前
Go语言怎么做链路追踪_Go语言分布式链路追踪教程【精选】.txt
jvm·数据库·python
Jaygee-2 小时前
WordPress 企业官网搭建教程:用 GMSSH 免费配好 HTTPS、WAF 和网站报表
java·数据库·https
m0_377618232 小时前
CSS如何实现背景颜色的棋盘格分布_利用repeating-gradient
jvm·数据库·python