default_statistics_target 是 PostgreSQL 里一个容易被忽视的 GUC(Grand Unified Configuration)。默认值 100 对大多数场景够用,但当你把它推到 500 甚至更高时,ANALYZE 的耗时会暴涨——不过在某些数据分布下,这个代价换来的是查询计划质的飞跃。
统计目标到底控制什么
PostgreSQL 的查询优化器不看原始数据,它看的是 pg_statistic 里存的统计摘要。default_statistics_target 决定 ANYZE 在每列上采集多少个最常见值(MCV)和多少个直方图桶。值越大,摘要越精细,优化器对行数估算越准确;代价是 ANALYZE 要扫更多行、算更多统计量。
默认 100 意味着最多 100 个 MCV 和 100 个直方图桶。对于只有几十种取值的枚举列,100 已经远超需要;但对于有百万种取值且分布极度倾斜的列,100 个桶可能根本捕捉不到关键拐点。
调到 500 时会发生什么
Christophe Pettus 在演讲中直指核心:把 default_statistics_target 从 100 改到 500,ANALYZE 会明显变慢,在大表上甚至"慢到永远"。原因很简单——采样行数从约 30,000 跳到约 150,000(采样行数 = 300 × statistics_target),统计计算量也同步放大。
但慢不是无意义的慢。如果你的查询计划因为统计不足而选了错误的 join 方式或错误的扫描路径,调高统计目标可能直接把一个跑几分钟的查询压到几秒。
什么时候值得调高
不是所有列都需要更精细的统计。值得调高的典型场景:
- 高基数、重倾斜列:比如用户 ID、订单金额、时间戳等,少数值占据大量行,其余值稀疏分布。默认 100 个桶无法捕捉倾斜点,优化器会严重低估或高估行数。
- 范围查询频繁的列:大量
WHERE price BETWEEN 100 AND 500类查询,直方图桶越多,边界估算越准。 - 多列统计(extended statistics):当列之间有函数依赖或关联时,配合
CREATE STATISTICS使用更高的目标才有意义。
反过来,枚举列、布尔列、低基数状态列,调高纯属浪费。
实操:按列设置,而不是全局拉高
最关键的建议——不要盲目改全局默认值。PostgreSQL 允许你对单列设置统计目标,这才是正确做法:
-- 先看当前值
SELECT attname, stadistinct, stanumbers1
FROM pg_stats
WHERE tablename = 'orders' AND attname = 'amount';
-- 对倾斜列单独调高
ALTER TABLE orders ALTER COLUMN amount SET STATISTICS 500;
-- 对低基数列反而可以降低,加速 ANALYZE
ALTER TABLE orders ALTER COLUMN status SET STATISTICS 50;
-- 触发重新收集统计
ANALYZE orders;
-- 验证新目标生效
SELECT attname, statistics_object
FROM pg_attribute
WHERE attrelid = 'orders'::regclass;
运行前注意:ANALYZE orders 在大表上会比之前慢,建议在低峰期执行。如果你用的是 autovacuum,它会自动按新目标采样,但首次生效仍需手动 ANALYZE 或等 autovacuum 轮到这张表。
确认调高是否真的改善了计划
调完之后不要只看 ANALYZE 耗时,要看优化器的估算是否变准了:
-- 开启估算细节
EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM orders
WHERE amount BETWEEN 1000 AND 5000;
-- 对比调高前后的 rows= 估算值与实际行数
-- 估算接近实际 = 统计目标够用
-- 估算偏差仍然大 = 可能需要继续调高或加 extended statistics
如果估算从偏差 10 倍缩小到 2 倍以内,500 的目标就值了。如果改善不明显,说明问题不在统计粒度,可能在数据分布本身需要多列统计或手动提示。
多列统计配合高目标
单列统计再精细也解决不了列间关联。比如 city 和 zip_code 几乎一一对应,优化器不知道这一点,会低估组合过滤的行数:
-- 创建多列统计,目标同样可以调高
CREATE STATISTICS orders_city_zip (ndistinct, dependencies)
ON city, zip_code FROM orders;
-- 多列统计对象也受 default_statistics_target 控制
-- 如果全局是 100,这里也只采 100 级
-- 可以在创建后对整张表调高,或单独处理
ALTER TABLE orders ALTER COLUMN city SET STATISTICS 300;
ALTER TABLE orders ALTER COLUMN zip_code SET STATISTICS 300;
ANALYZE orders;
决策清单
在动 default_statistics_target 之前,走一遍这个流程:
- 用
EXPLAIN (ANALYZE)找出行数估算偏差大的查询。 - 定位偏差来自哪一列(看
pg_stats的correlation、n_distinct、MCV 覆盖率)。 - 对那一列单独
SET STATISTICS,从 200 开始试,不要一步跳到 500。 - 手动
ANALYZE后重新跑EXPLAIN (ANALYZE),对比估算与实际。 - 确认改善后,观察 autovacuum 在该表上的耗时是否可接受。
- 全局默认值只在大多数表都需要更高统计时才改——这种情况很少见。
统计目标是 PostgreSQL 优化器与真实数据之间的翻译器。翻译精度够用就行,过度精确的代价是 ANALYZE 拖慢整张表的维护节奏。按列精准投放,才是把 GUC 用到正确位置的方式。