代价比较的变革——PostgreSQL执行计划干预增强深度解析

本文整理于 HOW 2026 演讲内容,演讲者:杨向博,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要求走索引扫描,执行计划却依然走了并行顺序扫描。

sql 复制代码
/* 通过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是一个整型计数器,遵循层层累积与传递的原则:

  1. 扫描层 :当SET enable_seqscan TO OFF时,顺序扫描路径的disable_nodes从默认的0变为1
  2. 连接层disable_nodes值向上层层传递并递增累积
  3. 路径比较 :优化器优先比较disable_nodes的值,越小越优
  4. 一票否决 :如果一条路径的disable_nodes大于另一条,直接淘汰,不再比较代价
sql 复制代码
-- 禁用顺序扫描
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及以前)

  1. 比较Total Cost(1%模糊)
  2. 比较Startup Cost

新逻辑(PG 18+)

  1. 优先比较disable_nodes(一票否决)
  2. 如果disable_nodes相同,再比较Total Cost(1%模糊)
  3. 如果Total Cost相近,再比较Startup Cost

3.4 效果验证

在PG 18中,同样的HINT指定索引扫描:

sql 复制代码
/*+ 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计划管理) 的方向:

  1. 计划捕获:自动识别高频或关键SQL语句
  2. 计划分析:将执行计划向量化,结合AI进行相似性分析与性能评估
  3. 反馈闭环:基于实际执行耗时(含系统负载因素),持续调整计划选择策略
  4. 自动演进:让优化器在同样的机器、同样的负载条件下,通过不断学习,自动选择更优的执行计划

这并不是要替代传统的CBO优化器,而是在CBO的基础上增加一个AI辅助决策层。当优化器的代价预估与实际执行出现偏差时,AI层可以介入干预,通过Hint机制"拨正"执行计划。两者相辅相成------CBO负责快速的路径生成与基础代价估算,AI层负责在代价模型失准时进行精准纠偏,共同构建更智能、更稳健的执行计划管理系统。

结语

PostgreSQL 18引入的disable_nodes禁用节点机制,是执行计划干预能力的一次质的飞跃 。它从根本上解决了传统disable_cost粗暴加价模式导致的HINT失效问题,让DBA和开发者能够精准、可靠地控制执行计划

这一改进的意义远超技术层面------它意味着PG在执行计划治理的确定性上迈出了关键一步,为解决因优化器代价偏差导致的性能问题提供了坚实的基础设施。

与此同时,AI驱动的学习型优化器正在为未来打开新的可能。在不远的将来,数据库或许能够自动学习、自动优化,在CBO的基础上叠加AI辅助决策层,让执行计划的选择既能继承CBO的高效路径生成能力,又能通过AI层精准纠偏,实现真正的"智能优化"。

届时,DBA的工作重心将从"被动救火"转向"主动治理",这将是数据库运维的又一次范式转移。

相关推荐
SL-staff1 小时前
离散制造企业如何应对频繁插单?智能排产系统带来新思路
大数据·数据库·人工智能·技术教程·智能排产·jvs-aps·排产系统
码云数智-园园1 小时前
Oracle 锁等待、会话阻塞故障复盘:生产事故排查步骤
数据库·oracle
哈__2 小时前
从 MySQL 到在线表格:NocoDB 自托管部署、数据导入与远程访问
数据库·mysql
倔强的石头1063 小时前
高可用与无感扩容——分布式时序数据库选型指南
数据库·分布式·时序数据库
袋鼠云数栈12 小时前
实时湖仓如何真正做到“数据够新”?
大数据·数据库·人工智能·数据治理
ACP广源盛1392462567312 小时前
M6/M5 Pro Mac mini 端侧 AI 落地@ACP#YLB3116 中端多盘存储扩展在 AI 服务中的机会与应用场景
大数据·网络·数据库·人工智能·嵌入式硬件·macos
ACP广源盛1392462567312 小时前
M6/M5 Pro Mac mini 端侧 AI 新形态@ACP#GSV5800 Serdes 长距离视频传输在 AI 服务中的机会与落地场景
大数据·网络·数据库·人工智能·嵌入式硬件·macos·音视频
倔强的石头_14 小时前
事务边界与批量写入:避免长事务、锁等待和日志压力
数据库
努力努力再努力wz14 小时前
【Redis入门系列】从 KEYS 到 SCAN:渐进式遍历、Cursor 与位反转原理
数据库·redis·缓存