ANALYZE 更新的是 PostgreSQL 用来估算查询结果的统计模型。它不会重写查询、重建索引、回收死元组,也不承诺执行计划一定变快。它回答的具体问题是:规划器是否足够了解当前数据分布,能够合理比较不同访问路径?即使表里的有效行总数完全没变,一次批量状态更新也足以让原来的分布信息过时。先明确这个边界,才能判断刷新统计是否解决了眼前的问题。
统计描述的是什么
对于普通列,pg_stats 可以展示空值比例、常见值及其频率、不同值数量的估计和直方图等信息。规划器把它们与表的大小、成本参数一起使用。任务表中只有百分之一处于就绪状态,与百分之九十已经就绪,意味着完全不同的读取工作量。前者可能适合通过索引定位少量行,后者直接顺序读取整张表可能更合适。因此,“没有走索引”本身不等于优化失败,关键仍然是这个条件究竟会取出多少数据。
大表上的分析通常抽样,而不是逐行检查全部内容。更详细的统计需要更大的样本和更多工作,不能由“抽样”推导出“任何时候都很便宜”。即使业务数据没有变化,不同轮抽样产生的估计也可能略有波动。针对特定列提高统计目标,有助于更好地表达偏斜分布,但普通的单列统计不会因此自动学会所有列之间的关联。样本精度不足和模型缺少相关性信息,是两种不同的问题。
坏掉的样子
批量更新以后,旧的常见值频率可能严重低估过滤结果。规划器于是安排了实际需要重复很多次的嵌套循环,或者选择一个最终要读取大部分表内容的索引扫描。不过,查询本来就要返回大多数行时,顺序扫描是合理候选,不应该为了恢复过去的节点名称而强制索引。
还有一些变慢根本不是统计导致的。查询正在等待另一个事务释放锁时,重复分析不会把锁释放;统计刷新也不会消除应用连接池排队或凭空建立缺失的索引;所需顺序仍须通过排序或已有的有序路径提供。应当先找出计划中最早出现的有效行数偏差,再将它与真正耗时的环节对应起来,而不是把所有延迟都归结为统计陈旧。
快速验证
在可丢弃的 PostgreSQL 18 数据库中使用 psql 执行下面代码。实验把临时表放在事务里,最后回滚清理。这里特意使用临时表:自动清理进程不能分析其他会话的临时表,因此不会在两次受控观察之间悄悄刷新本实验的统计。
\set ON_ERROR_STOP on
BEGIN;
CREATE TEMP TABLE jobs (id integer,ready boolean,payload text);
INSERT INTO jobs SELECT g,g<=100,repeat('x',100) FROM generate_series(1,10000) AS g;
CREATE INDEX jobs_ready ON jobs(ready);
ANALYZE jobs;
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF) SELECT id FROM jobs WHERE ready;
UPDATE jobs SET ready=true WHERE id<=9000;
SELECT most_common_vals,most_common_freqs FROM pg_stats
WHERE schemaname=(SELECT nspname FROM pg_namespace WHERE oid=pg_my_temp_schema())
AND tablename='jobs' AND attname='ready';
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF) SELECT id FROM jobs WHERE ready;
ANALYZE jobs;
SELECT most_common_vals,most_common_freqs FROM pg_stats
WHERE schemaname=(SELECT nspname FROM pg_namespace WHERE oid=pg_my_temp_schema())
AND tablename='jobs' AND attname='ready';
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF) SELECT id FROM jobs WHERE ready;
DO $$ BEGIN
IF (SELECT count(*) FROM jobs WHERE ready) <> 9000 THEN RAISE EXCEPTION 'data changed during analysis'; END IF;
END $$;
ROLLBACK;
在 PostgreSQL 18.6 的实测中,第一次查询通过 jobs_ready 估计并返回一百个就绪任务。接着的更新把就绪任务增加到九千个。再次分析之前,pg_stats 仍然给出 {f,t} 和 {0.99,0.01},说明统计模型没有学到这次分布变化。表中存活记录的总数仍然是一万,变化的是满足条件的比例。
中间那份计划估计返回一百九十行,实际返回九千行。不要把预期写成“估计必然还停在一百”:更新也改变了关系的物理大小,PostgreSQL 可以据此缩放关系行数估计。真正揭示陈旧信息的是仍为百分之一的常见值频率,而不是一个必须冻结不变的行数。这个区别也解释了为什么仅仅比较前后表的总行数,不能完整判断统计是否仍然适用。
第二次 ANALYZE 后,常见值顺序变为 {t,f},频率变为 {0.9,0.1}。本环境选用了顺序扫描,估计与实际结果都是九千行。末尾断言检查满足条件的数据数量没有被分析操作修改。其他环境中的计划形状、成本和耗时可能不同;这里证明的是分布信息被刷新、返回数据正确,不是某种扫描在所有机器上都必然更快。
根据证据安排刷新
普通持久表通常会在变更积累到一定程度后由自动清理进程安排分析,因此不应默认把所有表的触发阈值都调低。先核对分析是否确实落后于负载,再决定调整对象。批量导入或已知的大规模分布变化,可以成为在恢复敏感流量之前显式分析相关表的理由。
分区父表还需要单独关注:叶子分区的自动分析并不会自动维护父表覆盖整个层级的统计。临时表也需要拥有它的会话主动分析。在 PostgreSQL 18 中,表维护权限决定调用者能否分析相应对象;要查看跳过表的消息,不能只凭命令结束就认定所有目标都刷新成功。最后重新核对受影响的计划和应用延迟。如果行数模型已经合理,而查询仍然慢,就应当沿实际证据转向其他瓶颈。
来源
- PostgreSQL 18:ANALYZE,抽样与统计刷新的边界,已核验 2026-10-15。
- PostgreSQL 18:规划器使用的统计,列分布与扩展统计的限制,已核验 2026-10-15。