代价比较的变革——PostgreSQL执行计划干预增强深度解析
1%的模糊代价比较为何会让LIMIT更慢、HINT失灵,PG 18新增的disable_nodes禁用节点机制又是如何让执行计划干预变得精准可靠的。
Nickyoung
PostgreSQL ACE
前言
在数据库系统的日常运维中,SQL执行计划“跑偏”是最令DBA头疼的问题之一。明明一条SQL语句,不加LIMIT时执行飞快,加了LIMIT反而慢了几千倍;明明通过HINT指定了索引,优化器却置若罔闻,走了顺序扫描。这些看似“降智”的行为背后,究竟隐藏着怎样的机制?
本文基于HOW 2026开源生态大会暨PostgreSQL高峰论坛的主题分享,系统剖析PostgreSQL优化器在代价比较与路径选择中的核心逻辑,解读PG 18在路径禁用机制上的重大变革,并展望AI驱动执行计划自动优化的未来方向。
一、案发现场:两桩“悬案”
1.1 悬案一:LIMIT反而更慢
在PostgreSQL中,有时会出现一种反直觉的现象:带LIMIT的查询,执行耗时反而比不带LIMIT的查询更久。

某实际场景中,不带LIMIT的查询执行仅需0.1毫秒,而带LIMIT的查询却耗时3秒以上——差距高达数万倍。
根本原因在于:PostgreSQL的LIMIT算子没有充分考虑真实的数据分布,在代价预估(Cost Estimation)环节选错了索引,导致回表时间急剧增加。
1.2 悬案二:HINT“失灵”
在PG 17及更早版本中,使用pg_hint_plan插件指定索引时,会出现一种令人费解的现象——明明通过HINT要求走索引扫描,执行计划却依然走了并行顺序扫描。
/* 通过HINT指定走特定索引 */ /*+ IndexScan(tbl tbl_name_idx) */ SELECT ... FROM tbl WHERE name = 'xxx';
执行结果显示走了并行顺序扫描,HINT仿佛完全被忽略。
这一问题的根源,要从PG优化器的代价比较机制说起。
二、源码追凶:1%的“模糊代价比较”
2.1 优化器决策流程的认知误区
在分析具体问题之前,首先需要纠正一个关于CBO(Cost-Based Optimizer,基于代价的优化器)的常见误区。
很多人认为:优化器生成所有可能的执行路径后,通过比较最终总代价(Total Cost),选出代价最小的路径作为最优执行计划。
但实际上并非如此。
在PG中,路径的生成与淘汰是逐层进行的。在add_path()函数中,新生成的路径会与已有路径进行比较,不具备显著优势的路径会被立即淘汰,而不会等到所有路径生成完毕后再做决策。

2.2 幕后黑手:1%的模糊比较系数
PG优化器中存在一个名为COST_EPSILON的模糊比较系数,默认值为1%。
其核心逻辑是:当两条路径的总代价(Total Cost) 差异在1%以内时,优化器认为二者“代价相等”,此时会进一步比较启动代价(Startup Cost),取启动代价更小的路径。
回到HINT失灵的案例:

使用HINT指定索引扫描时,pg_hint_plan插件通过设置一个巨大的disable_cost值(100亿级别,定义为DBL_MAX/100000.0)来“惩罚”其他路径,试图让目标路径在代价比较中胜出。
然而,当所有路径的代价都被加上一个巨大的常量后,原本存在的代价差异被压缩进1%的模糊区间内。优化器无法分辨优劣,转而比较启动代价,最终可能选出非预期的路径——比如并行顺序扫描。
这就是为什么HINT在极端代价偏差下会“失灵”的根本原因。
2.3 另一桩悬案:244秒 vs 287毫秒
再看另一个典型案例。
一条涉及外部表关联的查询,优化器默认选择了Nest Loop Join,执行耗时高达244秒。
通过SET enable_nestloop TO OFF强制禁用Nest Loop后,优化器选择了Hash Join,执行耗时骤降至287毫秒——差距近千倍。

进一步分析执行计划发现:
| 路径 | Total Cost | Startup Cost | 执行耗时 |
|---|---|---|---|
| Nest Loop | 232.45 | 200.00 | 244秒 |
| Hash Join | 232.44 | 216.13 | 287毫秒 |
仔细观察Total Cost:232.45与232.44,差距仅0.01,占总代价的约1/23200 ≈ 0.004%,远小于1%的阈值。
两条路径的总代价差异在1%以内,被优化器视为“相等”。随后比较启动代价(Startup Cost):Nest Loop的启动代价为200,Hash Join为216.13——Nest Loop胜出。
于是,Hash Join路径在add_path()阶段被直接淘汰,根本没能进入最终的路径候选集。
这就是1%模糊比较导致的“错杀”悲剧。
2.4 为什么是1%?
PG的代价(Cost)数值通常保留到小数点后两位(百分位)。最小可表示的代价差异即为0.01。
假设两条路径的总代价在100左右,0.01的差异恰好对应约0.01%。而1%的阈值是为了规避浮点运算误差带来的不确定性判断——当差异小于1%时,优化器认为它们在统计误差范围内“等价”,转而比较其他维度(如启动代价)。
问题在于:真实的执行性能差异,与优化器代价预估的百分比差异,并不成正比。
232.44与232.45只差0.01,但实际执行时间却差了近千倍——这充分暴露了优化器代价模型与实际执行之间的偏差。
三、重塑规则:PG 18的颠覆性变革
3.1 从“加价”到“禁用”
针对上述问题,PG 18引入了一项重大改进——路径禁用机制。
该改进由Robert Haas(社区Core Team成员)主导提交,在PG 18的第一个版本中即已合入。郭峰(Richard Guo)等中国贡献者也参与了相关讨论。
旧逻辑(PG 17及以前) :
- 通过添加巨大的
disable_cost值来“惩罚”非目标路径 - 弊端:巨大常量会压缩路径间的代价差异,导致模糊比较失效
新逻辑(PG 18+) :
- 在
Path结构体中新增整型变量disable_nodes - 彻底移除
disable_cost逻辑 disable_nodes在路径比较中拥有一票否决权
3.2 禁用节点计数(disable_nodes)的传递机制
disable_nodes是一个整型计数器,遵循层层累积与传递的原则:
- 扫描层:当
SET enable_seqscan TO OFF时,顺序扫描路径的disable_nodes从默认的0变为1 - 连接层:
disable_nodes值向上层层传递并递增累积 - 路径比较:优化器优先比较
disable_nodes的值,越小越优 - 一票否决:如果一条路径的
disable_nodes大于另一条,直接淘汰,不再比较代价
-- 禁用顺序扫描 SET enable_seqscan = OFF; -- 对应路径的 disable_nodes = 1 -- 禁用Nest Loop SET enable_nestloop = OFF; -- 对应路径的 disable_nodes = 1 -- 如果一条路径同时使用了顺序扫描和Nest Loop -- 其 disable_nodes = 1(scan层)+ 1(join层)= 2 -- 在路径比较中会优先被淘汰
3.3 新的路径比较逻辑
在compare_path_costs_fuzzily()函数中,比较顺序发生了变化:
旧逻辑(PG 17及以前) :
- 比较Total Cost(1%模糊)
- 比较Startup Cost
新逻辑(PG 18+) :
- 优先比较disable_nodes(一票否决)
- 如果disable_nodes相同,再比较Total Cost(1%模糊)
- 如果Total Cost相近,再比较Startup Cost

3.4 效果验证
在PG 18中,同样的HINT指定索引扫描:
/*+ IndexScan(tbl tbl_name_idx) */ SELECT ... FROM tbl WHERE name = 'xxx';
执行计划精准地走向了指定索引,耗时从3秒以上降至0.1毫秒。
执行计划中会显示Disabled Nodes = true标记,清晰指示哪些路径被禁用。Cost数值不再被巨大常量扭曲,回归正常范围。

3.5 核心改进总结
| 维度 | PG 17及以前 | PG 18+ |
|---|---|---|
| 核心机制 | disable_cost(粗暴加价) | disable_nodes(精确禁用) |
| 比较优先级 | Total Cost → Startup Cost | disable_nodes → Total Cost → Startup Cost |
| HINT成功率 | 极端代价下会失效 | 100%精准命中 |
| 代价扭曲 | 巨大常量扭曲模糊比较 | 无扭曲,完全隔离 |
| 人为干预 | 偶尔不可靠 | 指哪打哪 |
四、未来蓝图:AI驱动的执行计划自动优化
4.1 优化器代价偏差的根源
即便有了PG 18的精确禁用机制,优化器自身的代价预估偏差仍然客观存在。偏差的根源主要包括:
- 静态统计信息:基于历史采样的统计信息无法完全反映真实数据的动态特征
- 信息滞后:统计信息的收集存在延迟
- 均匀分布假设:优化器假设数据均匀分布,与实际数据分布往往不符
- 忽略系统负载:代价预估中完全不考虑当前系统负载(如Load Average),可能导致高负载下仍选择并行计划,引发系统崩溃
4.2 学习型优化器(Learned Optimizer)
传统的CBO正面临来自AI方向的挑战与补充——学习型优化器应运而生。
其核心思路是:利用机器学习/强化学习技术,通过HINT作为干预手段,持续采集执行反馈(Feedback),自动学习不同负载下的最优执行计划。
实验测试(基于Babylon测试集)显示:
- 模型在初始学习/探索阶段,延迟存在一定波动
- 完成训练后,延迟曲线显著低于原生PG优化器的基准线(蓝色曲线)
- 效果在PG 18上已得到初步验证
4.3 借鉴Oracle SPM的思路
更长远来看,数据库执行计划的治理应走向类似Oracle SPM(SQL Plan Management,SQL计划管理) 的方向:
- 计划捕获:自动识别高频或关键SQL语句
- 计划分析:将执行计划向量化,结合AI进行相似性分析与性能评估
- 反馈闭环:基于实际执行耗时(含系统负载因素),持续调整计划选择策略
- 自动演进:让优化器在同样的机器、同样的负载条件下,通过不断学习,自动选择更优的执行计划
这并不是要替代传统的CBO优化器,而是在CBO的基础上增加一个AI辅助决策层。当优化器的代价预估与实际执行出现偏差时,AI层可以介入干预,通过Hint机制“拨正”执行计划。两者相辅相成——CBO负责快速的路径生成与基础代价估算,AI层负责在代价模型失准时进行精准纠偏,共同构建更智能、更稳健的执行计划管理系统。
结语
PostgreSQL 18引入的disable_nodes禁用节点机制,是执行计划干预能力的一次质的飞跃。它从根本上解决了传统disable_cost粗暴加价模式导致的HINT失效问题,让DBA和开发者能够精准、可靠地控制执行计划。
这一改进的意义远超技术层面——它意味着PG在执行计划治理的确定性上迈出了关键一步,为解决因优化器代价偏差导致的性能问题提供了坚实的基础设施。
与此同时,AI驱动的学习型优化器正在为未来打开新的可能。在不远的将来,数据库或许能够自动学习、自动优化,在CBO的基础上叠加AI辅助决策层,让执行计划的选择既能继承CBO的高效路径生成能力,又能通过AI层精准纠偏,实现真正的“智能优化”。
届时,DBA的工作重心将从“被动救火”转向“主动治理”,这将是数据库运维的又一次范式转移。


