PostgreSQL 统计目标调高之后,ANALYZE 为什么会卡住——以及什么时候该这么做

2026-06-12 19 预计阅读时间: 1 分钟
来源: postgr.es AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:7 分钟

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 的目标就值了。如果改善不明显,说明问题不在统计粒度,可能在数据分布本身需要多列统计或手动提示。

多列统计配合高目标

单列统计再精细也解决不了列间关联。比如 cityzip_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 之前,走一遍这个流程:

  1. EXPLAIN (ANALYZE) 找出行数估算偏差大的查询。
  2. 定位偏差来自哪一列(看 pg_statscorrelationn_distinct、MCV 覆盖率)。
  3. 对那一列单独 SET STATISTICS,从 200 开始试,不要一步跳到 500。
  4. 手动 ANALYZE 后重新跑 EXPLAIN (ANALYZE),对比估算与实际。
  5. 确认改善后,观察 autovacuum 在该表上的耗时是否可接受。
  6. 全局默认值只在大多数表都需要更高统计时才改——这种情况很少见。

统计目标是 PostgreSQL 优化器与真实数据之间的翻译器。翻译精度够用就行,过度精确的代价是 ANALYZE 拖慢整张表的维护节奏。按列精准投放,才是把 GUC 用到正确位置的方式。


相关推荐