PostgreSQL 的查询规划器靠一组常量给每条执行路径"打分",最终选出成本最低的那个方案。其中 cpu_tuple_cost、cpu_index_tuple_cost 和 cpu_operator_cost 是最常被提及的三个。但最实用的一条建议是——你几乎永远不该修改它们。下面解释原因,以及在真正需要调优时该做什么。
规划器怎么用这三个常量
规划器的成本模型把每条路径的代价拆成两部分:I/O 代价(读磁盘页)和 CPU 代价(处理一行、比较一个值)。三个常量负责后者:
| 常量 | 默认值 | 含义 |
|---|---|---|
cpu_tuple_cost |
0.01 | 处理一个结果行的 CPU 开销 |
cpu_index_tuple_cost |
0.005 | 从索引扫描中取出一个索引条目的 CPU 开销 |
cpu_operator_cost |
0.0025 | 执行一个简单运算符(比较、加法等)的 CPU 开销 |
它们之间的比例关系才是关键:索引条目比完整行便宜一半,运算符比索引条目再便宜一半。规划器用这套比例来判断"扫描多少索引条目后,走顺序扫描反而更划算"。如果你只改其中一个,比例就乱了。
为什么有人想改它们
最常见的动机是:EXPLAIN 显示规划器选错了路径。比如一张大表上,明明索引扫描更快,规划器却选了顺序扫描。有人就试图降低 cpu_index_tuple_cost,让索引路径的评分更低,"骗"规划器走索引。
问题在于——规划器选错路径,根源几乎不在这三个常量,而在统计信息不准。
改常量的真实风险
改掉任何一个常量,影响的不是一条查询,而是整个数据库所有查询的规划决策:
- 你为某条查询调低了
cpu_index_tuple_cost,其他本该走顺序扫描的查询也被迫走索引,反而更慢。 - 你调低了
cpu_tuple_cost,规划器会更偏好返回少量行的计划,但那些需要处理大量中间行的复杂查询可能被误导。 - 三个常量的比例一旦失衡,规划器对"索引扫描 vs 顺序扫描"的临界点判断就会系统性偏移。
这就是为什么原文强调:这些常量是规划器内部模型的"校准参数",不是给用户调优的旋钮。
该做什么:先查统计信息,再考虑局部干预
遇到规划器选错路径,按这个顺序排查:
1. 检查统计信息是否过时或精度不足
-- 查看表的统计信息更新时间和行数估计
SELECT relname, reltuples, last_autovacuum, last_autoanalyze
FROM pg_stat_user_tables
WHERE relname = 'your_table';
-- 手动刷新统计信息(最常见的一步就能解决问题)
ANALYZE your_table;
-- 如果表很大、数据分布倾斜,提高统计目标
ALTER TABLE your_table ALTER COLUMN your_column SET STATISTICS 500;
ANALYZE your_table;
提高 STATISTICS 后,pg_stats 中 n_distinct 等估计会更精确,规划器对行数估算的偏差会缩小,路径选择自然更合理。
2. 用 EXPLAIN 对比实际与估计
-- 先看估计行数
EXPLAIN SELECT * FROM orders WHERE status = 'shipped';
-- 再看实际行数(需要实际执行)
EXPLAIN ANALYZE SELECT * FROM orders WHERE status = 'shipped';
如果 rows=100(估计)但实际 rows=50000,差距就是统计信息的问题,不是代价常量的问题。
3. 只在确认统计信息无误、且问题局限在少数查询时,才用局部手段
不要改全局常量。用以下方式只影响特定查询:
-- 方式一:在会话中临时设置,只影响当前连接
SET cpu_index_tuple_cost = 0.003;
EXPLAIN ANALYZE SELECT * FROM orders WHERE status = 'shipped';
-- 验证完毕后恢复
RESET cpu_index_tuple_cost;
-- 方式二:用 pg_hint_db 扩展直接指定路径(更可控)
/*+ IndexScan(orders orders_status_idx) */
EXPLAIN ANALYZE SELECT * FROM orders WHERE status = 'shipped';
pg_hint_db 让你用注释语法强制指定扫描方式,不改任何全局参数,影响范围仅限那条查询。
4. 如果确实要改全局常量,先量化影响
-- 改之前,记录当前值
SHOW cpu_tuple_cost; -- 0.01
SHOW cpu_index_tuple_cost; -- 0.005
SHOW cpu_operator_cost; -- 0.0025
-- 用 pgbench 或自定义脚本跑一批典型查询的基准耗时
-- 然后在测试环境改值
SET cpu_index_tuple_cost = 0.003;
-- 再跑同一批查询,对比总耗时和计划变化
-- 只有整体变好、且没有查询严重退化,才考虑保留
一个快速自查脚本
把下面脚本存为 check_gucs.sql,在需要时跑一遍,确认当前值是否偏离默认:
SELECT name, setting, unit, boot_val, source
FROM pg_settings
WHERE name IN ('cpu_tuple_cost', 'cpu_index_tuple_cost', 'cpu_operator_cost')
ORDER BY name;
boot_val 是编译时默认值,source 显示值来自哪里。如果 source 不是 default,说明有人改过,需要追溯原因。
小结
cpu_tuple_cost/cpu_index_tuple_cost/cpu_operator_cost是规划器内部模型的校准参数,三者比例关系比绝对值更重要。- 改它们会影响全库所有查询,风险远大于收益。
- 规划器选错路径,九成是统计信息问题:先
ANALYZE,再调STATISTICS,最后才考虑局部干预。 - 如果必须干预,优先用会话级
SET或pg_hint_db,不要改全局配置。 - 任何全局修改都要在测试环境量化全库影响,确认没有严重退化后再上线。