analyze后执行计划未变是因为它只更新pg_statistic而不刷新查询计划缓存;已有会话中的缓存计划(如prepared statement或连接池固化计划)不会自动重编译,需手动触发重计划或清理缓存。

为什么 ANALYZE 后执行计划还是没变?
因为 ANALYZE 只更新 pg_statistic,不强制刷新查询计划缓存;已有会话里缓存的执行计划(尤其是 prepared statement 或长期连接中的 plan)不会自动重编译。
常见错误现象:EXPLAIN 显示用了索引,但实际慢得离谱;pg_stat_all_tables 里 n_mod_since_analyze 很高,但刚跑过 ANALYZE 却没效果。
- 必须让 PostgreSQL 重新生成执行计划:对普通查询,改写 SQL(比如加个无意义注释
-- force re-plan)或断开重连;对 prepared statement,用DEALLOCATE后重新PREPARE - 检查是否被
PREPARE或连接池(如 pgbouncer 的 transaction 模式)固化了计划——这种情况下,ANALYZE本身没错,错在计划没机会更新 -
ANALYZE默认只采样部分行,大数据表可能需要显式加大采样率:ANALYZE table_name (column_name) WITH (sample_rate => 0.1)
pg_statistic 里哪些字段真正影响执行计划选择?
PostgreSQL 优化器主要依赖 pg_statistic 中的分布统计,不是所有字段都参与决策。关键字段只有几个,且有明确使用逻辑。
使用场景:排查“明明数据倾斜严重,却选了全表扫描”这类问题时,应直奔这几个字段查。
-
stakind1–5:标记统计类型,1是最常用值(MCV),2是数值直方图,3是空值比例——如果stakind1 = 0,说明 MCV 没采集,等价于“优化器不知道哪个值最常出现” -
stanumbers1(配合stakind1 = 1):存储高频值频次,若为空或长度为 0,WHERE x = 'common_value'就容易低估选择率 -
stadistinct:估算列的唯一值数量,若为负数(如-1),表示“唯一值极多”,此时优化器倾向用索引;若为正小整数(如5),则认为该列高度重复,可能跳过索引
什么时候该手动 ANALYZE,而不是等 autovacuum?
autovacuum 的 analyze_threshold 和 analyze_scale_factor 是按比例触发的,对小表敏感、大表迟钝;业务写入模式一变,就容易脱节。
性能影响:手动 ANALYZE 默认阻塞写操作(除非加 CONCURRENTLY,但仅限 14+ 且不支持所有场景),所以得挑低峰,但不能因怕影响而放任统计过期。
- 批量导入后必须立刻
ANALYZE:比如用COPY导入 100 万行,即使表原有 1 亿行,autovacuum 也可能几小时后才动——这期间所有新查询都基于旧统计 - 某列值分布突变后:例如用户等级字段从 “90% 是 L1” 突然变成 “70% 是 L5”,旧 MCV 列表完全失效,
ANALYZE是最快修正方式 - 建完新索引后建议跟一个
ANALYZE:索引本身不触发统计更新,优化器不知道新索引能覆盖哪些值分布
ANALYZE 的粒度怎么控制才不浪费资源?
全表 ANALYZE 在大表上耗时长、锁表久;盲目分析所有列,反而让 pg_statistic 膨胀,还可能干扰优化器做选择。
参数差异:ANALYZE table 是默认行为;ANALYZE table (col1, col2) 只更新指定列;VACUUM ANALYZE 是组合命令,但会先 VACUUM 再 ANALYZE,中间可能有数据变更导致统计不准。
- 优先分析
WHERE、JOIN、ORDER BY中出现的列,特别是组合条件里的前导列(如WHERE a = ? AND b > ?,重点分析a和(a,b)复合统计) - 避免分析明显无区分度的列:比如全为
NULL或固定值的列,ANALYZE不仅白费时间,还会在pg_statistic里留下误导性stadistinct = 1 - 对分区表,不要对父表
ANALYZE(除非启用了INHERIT统计),而是逐个分析活跃子分区——父表统计为空是正常现象,不代表出错
统计信息不是“设一次就永远有效”的配置,它和数据一样是动态的。最容易被忽略的是:你看到的执行计划,可能根本不是基于最新统计生成的——哪怕 pg_statistic 表里时间戳已经更新了。










