☰
先判定后评估:数据库查询优化器如何破局代价模型困境
2026/10/22 5:20:57 网站建设 项目流程

做数据库的人都知道,查询优化器是数据库内核里“智能程度”最高的模块,也是问题最多的地方。同一条SQL,执行计划选对了可能毫秒级返回,选错了可能直接拖垮整个业务。传统上,优化器靠代价模型在候选执行计划里挑最优,但候选数量、统计信息失真、估算误差这三件事叠加在一起,让“最优”越来越像一个美好的愿望。金仓数据库(KingbaseES)提出的“先判定后评估”框架,思路其实很朴素:它先把大量不值得精确评估的计划用低成本规则快速过滤掉,再对真正有竞争力的少数计划做精细代价计算。这篇文章我想从一个实践者的角度,把这个框架的设计逻辑、落地场景和常见问题一层层拆开讲透,也会聊聊我们自己写SQL、调执行计划时能借鉴的“先判定”思维。

1. 为什么需要“优化革命”:传统代价评估的三大痛点

1.1 代价估算的误差会一路放大

所有基于代价的优化器都逃不过一个问题:代价模型的输入是估算值,输出也是估算值。一条SQL经过解析、改写、生成执行计划,要估算每个算子的选择和投影基数。基数一旦估错,下游所有算子的代价都会跟着错。我见过一个真实场景:某报表查询中,一张历史表上的过滤条件命中率被统计为2%,实际因为数据倾斜命中率超过60%,优化器据此选择了哈希连接,结果排序算子内存溢出,整个查询跑了40多分钟。这种问题并不是个别现象,而是基于代价优化器的固有脆弱性。

代价模型里还有大量物理参数:CPU单行处理代价、随机I/O代价、顺序I/O代价、缓存命中率等。这些参数的默认值往往来自出厂配置,和真实运行环境的硬件水平不一定匹配。更麻烦的是,参数越多,模型就显得越“精致”,但输入误差反而越大。误差在多层算子之间迭代传播,最终选出来的执行计划和真正的最优计划可能差几个数量级。传统做法是用更多统计信息和直方图去“补救”,但补救本身又有成本,而且统计信息永远滞后于数据变化。

1.2 搜索空间爆炸让穷举变得不现实

多表连接时,连接顺序的组合呈指数级增长。8张表参与连接,理论上连接顺序就有8!种,再加上每种顺序下还有嵌套循环、哈希连接、归并连接等物理执行方式的排列组合,候选计划数量非常可观。如果每个候选计划都做完整代价估算,优化本身的开销就会大到不可接受。很多生产系统里的复杂查询,优化器消耗的时间甚至比执行时间还长,这显然不合理。

正是因为这个原因,成熟数据库普遍采用了剪枝策略,比如只考虑左深树、限制参与连接的最大表数、动态规划时按子树代价剪掉部分状态。金仓这套框架本质上也是一种剪枝策略,只是它把剪枝动作放在了代价评估之前,用更便宜的静态规则先筛一遍。进入动态规划的候选集合天然小了很多,优化器的压力也降下来了。换句话说,它没有去改变“最优解可以精算”的理想,而是先让精算变得可行。

1.3 统计信息失真时,“精密”反而变成负担

统计信息是代价模型的地基。表数据分布变化、直方图缺失、抽样比例不合理,都会让代价评估建立在错误的地基上。更麻烦的是统计信息更新有滞后性,比如业务大促期间数据量短时间内翻倍,而优化器还抱着旧统计信息做判断。这时候如果框架里全是“精密计算”,误差就会被精密地放大——错误的基数乘上错误的物理代价,最后得出一个看起来精确、实际上完全不靠谱的结论。

金仓这套框架的巧妙之处在于,判定阶段尽可能不依赖统计信息,只用表结构、索引、约束这些稳定信息做粗筛。即使统计信息已经失真,粗筛仍然能保证不走太差的路径。这就是它把“判定”放在“评估”之前的现实意义:与其赌统计信息准确,不如先靠结构信息避开明显的坑。对那些统计信息长期不准的系统来说,这一层保护尤其有价值。

2. 金仓“先判定后评估”框架到底怎么运作

2.1 一句话版本:先过滤,再精算

传统基于代价优化器的执行链路是:生成候选计划 → 对每个候选做代价估算 → 选择最小代价计划。金仓框架把它改成了:生成候选计划 → 先用静态规则快速判定 → 只对通过判定的候选做代价估算 → 选择最小代价计划。

这个改动看起来只是额外加了一步,意义却完全不同。判定阶段的成本极低,因为它用的都是结构层面的信息,不需要估算数据量,也不需要建立直方图。评估阶段的负担则被大幅减轻,因为它面前只剩下一小撮“真正有戏”的计划。判定阶段做的是排除错误答案,评估阶段做的是比较正确答案。这种方式不是推翻代价模型,而是让代价模型把精力花在值得花的地方。

2.2 判定阶段的“三类快筛规则”

我把判定规则归纳成三类,方便理解整个框架的覆盖面。

第一类是等价性判定。它利用关系代数的等价变换,判断两个候选计划是否产生相同结果。如果计划A和计划B逻辑等价,但A的算子链更短、访问路径更直接,就直接淘汰B。比如冗余子查询可以消除、谓词可以下推,诸如这类等价变换不需要知道数据量也能安全判定,属于“做了不会更差”的规则。这类判定的优势在于绝对安全,不会因为数据变化而失效。

第二类是可行性判定。有些计划物理上就不可行,或者明显低效。比如在某个没有索引的列上强行走索引扫描、在分区表上没有利用分区键裁剪而选择全表扫描、在非等值连接上使用哈希连接导致哈希桶严重倾斜。这类判定完全依赖元数据,几乎瞬时完成。生产环境里我发现,大量糟糕执行计划其实都属于这一类“不该被考虑的选项”,以前它们也混在候选队列里浪费优化器的时间,现在第一步就被挡在门外。

第三类是收益上界判定。这条最有意思。它可以粗略估算一个计划“最好情况下”的代价上界,再对比另一个计划“最坏情况下”的代价下界,如果上界仍然追不上对方的下界,这个计划可以直接判死。这种剪枝思路很像搜索算法里的启发式上界判断,不需要精确代价,只做数量级级别的比较就够了。比如一个计划就算缓存全部命中、I/O完全免费,另一个计划在最糟糕的磁盘情况下也肯定更快,那前者就没有继续评估的必要。

2.3 评估阶段的“精算”只剩下少数选手

通过判定的候选计划数量通常已经被压缩到很小的规模,这时候才轮到传统代价模型登场。在评估阶段,基数估计、直方图、物理代价参数都会得到更认真对待,因为候选数量少,优化器可以负担得起更细粒度的模拟,比如考虑缓存命中率、CPU并行度、内存限制等,而不是对所有候选都用粗糙的默认参数。这种粗筛在前、精算在后的设计,等于把优化器的算力集中到了刀刃上。

另外我注意到,这个框架还带来一个隐藏好处:评估阶段的误判率下降了。以前所有候选计划共用一套简单估算模型,注定只能算“大概”。现在候选数量少,估算模型可以做得更细致,比如针对少数计划做更符合实际硬件环境的参数修正。候选少了,单计划精度高了,整体选出来的计划自然更贴近真实最优解,这一点在复杂查询上表现得尤其明显。

3. 三个真实场景验证这套框架的实战效果

3.1 场景一:多表连接的顺序剪枝

假设有A、B、C三张表做连接,A表只有几百行,B表有几百万行,C表有上亿行。传统优化器会枚举所有连接顺序并逐一估算代价。金仓框架的判定阶段拿到表结构信息后,马上可以判定:以C表作为最内层驱动表的候选计划,行数级明显高于以A表为驱动表的计划。哪怕C表上有多个索引,驱动大表再逐行查找小表的代价,也不会优于驱动小表再查找大表。

所以判定阶段会快速留下一小撮以A表为驱动表的候选计划,再交给精算阶段比较具体的连接算法和索引选择。这里要注意,判定不能做得太绝对,否则可能漏掉反直觉的最优计划。比如A表虽然小,但如果连接键分布极度倾斜,小表驱动大表可能导致大表被反复扫描,这时反而应该用大表做驱动再哈希连接。因此判定阶段只能排除“明摆着不可能”的计划,真正有争议的候选必须留给精算去裁决。

3.2 场景二:子查询展开与半连接转化

数据库里过多的子查询往往是性能瓶颈的重灾区。嵌套的IN、EXISTS子查询如果原样执行,大多数情况下会退化成逐行反复执行的嵌套循环,效率极低。金仓框架在判定阶段会优先检查子查询是否具备展开条件:子查询里有没有聚合函数、是否存在相关列、连接键是不是唯一键。如果判定可以展开成半连接或反连接,就优先进入半连接计划路径。

我实际测过一个带EXISTS子查询的报表,展开成半连接后,执行时间从原来的18秒降到不足1秒。这是“先判定”带来的直接收益:它不只是省了优化时间,更帮执行器避开了最差的执行路径。这种判断如果靠人工来做,每个SQL都要反复试验;做成框架里的固定规则之后,每个查询都能自动享受同一种优化策略,收益非常稳定,尤其是在子查询满天飞的分析系统里。

3.3 场景三:参数化查询与分区裁剪

业务系统通常使用绑定变量来复用执行计划。参数化查询的麻烦在于优化器不知道参数的具体值,很难像常量查询那样做到精确估算。金仓的判定阶段会基于谓词列的结构特征做预处理:如果谓词列正好是分区键,就直接判定该查询可以走分区裁剪路径,只扫描相关分区。这样即使参数值未知,访问范围也已经预先确定了。

我遇到过很多次因为没有分区裁剪而全分区扫描的案例,事后分析执行计划,发现优化器其实拥有全部信息,只是没有“主动判定”分区键这一下。这套框架把这类判定固化成规则,相当于把最佳实践直接做进了优化器里,而不是依赖每个DBA手工改写SQL。对分库分表、按月分区的业务系统来说,这条规则带来的性能提升往往是最直观的,能从扫描30个分区变成扫描1个分区。

4. 对比RBO与CBO:改写游戏规则的关键差异

4.1 相比传统CBO:省的是“脑力”,不是“体力”

传统CBO面对大查询时,优化开销经常高到需要设置优化器超时时间,超时后只能匆忙选一个可行计划。金仓框架没有抛弃代价模型,它只是让代价模型面对更小的候选集合。如果说传统CBO是一个“对所有选项都做精细测算的会计”,那“先判定后评估”就是一个“先由老法师按经验毙掉明显不行的方案,再让会计细算剩余选项”的做法。

规则和经验是便宜的,精密计算应该留给少数关键决策。这一点在性能上的差距会随着查询复杂度放大:20张表的连接查询,传统优化器可能穷举到怀疑人生,而“先判定后评估”先把明显不合理的连接顺序和物理执行方式排除掉,再对少数重点计划做动态规划和代价比较,优化时间和最终计划质量都会有实质改善。省下来的“脑力”还可以用于更精细的运行时反馈,形成一个良性循环。

4.2 相比RBO:保留了对数据的尊重

早期规则优化器依靠的是死规则:哪种访问方式永远优先、哪种连接顺序永远先走。规则僵化,遇到规则没有覆盖的最优计划就无能为力。最典型的问题就是:索引存在时RBO一定走索引,但数据量只有几十行时全表扫描其实更快,RBO却不懂得变通。金仓框架不是从CBO退回RBO,它只是把判定阶段规则化,真正选择最优路径时依然通过代价评估完成,所以不会重蹈RBO“死板教条”的覆辙。

它用规则去解决“哪些计划不值得看”,用代价去解决“剩下哪些计划最好”,分工明确。判定规则即便固定,也不会伤害查询效率,因为它筛掉的本来就不是最优解;评估阶段则始终保持对数据分布的敏感性。这种组合让优化器既有规则的效率,又有代价模型的决策精度,是它相比传统架构最核心的差异。

4.3 边界情况和适用范围

再好的框架也有边界。表结构极复杂、统计信息严重过期、数据分布极度倾斜时,判定阶段也可能给出错误偏向,精算阶段基于错误统计信息选错计划。所以“先判定后评估”并不能替代统计信息维护,它只是让优化器在复杂环境下多了一层保护垫。那些依赖统计信息的判定和估算,依然是整个系统最薄弱的部分。

从适用范围看,这套框架对复杂分析型查询、大表连接、子查询嵌套这类场景收益最明显。简单点查本来就没有多少候选计划,判定阶段的作用相对有限。另外,如果某条SQL的统计信息常年不更新,任何优化器都很难救场。对DBA来说,基础工作还是要做:及时更新统计信息、合理设计索引、保持SQL形态稳定。框架负责降低选错概率,但不可能消除所有错误来源。

5. DBA和开发者能借鉴的“先判定”实操技巧

5.1 看执行计划时多问一个“为什么”

日常调优时,我习惯先把执行计划按算子拆开,先不看代价数字,先检查有没有违反“朴素常识”的路径:有没有明明有索引却不走、有没有明显可以先过滤再连接却把过滤放在最后。这种检查思路其实就是把“先判定”思维用在人工调优上。用EXPLAIN看计划时,第一步先判定计划结构是否合理,第二步才去看每一步的行数和耗时。

很多初学者一上来就盯着右手边的耗时数字,其实顺序错了。结构不合理的计划,数字再漂亮也是虚的。我会先快速找有没有“全表扫描 + 嵌套循环 + 大表驱动”这样的组合拳,一旦命中,基本可以判定这个计划没戏,再去人工干预就很高效。这种先粗后细的排查方式,和框架在优化器里做的事情一模一样。

5.2 SQL改写时的“先判定”思维

写SQL的时候也可以借用这套框架。拿到一个查询,先判断哪些表是真正必要的、哪些谓词能提前过滤、子查询能不能展开成JOIN。我经常跟开发同事说,先别急着把SQL堆成一大坨,先把查询意图里“不可能有用的数据范围”判定清楚,再让SQL自然表达。这样写出来的SQL更简单,执行计划也更稳定。

举个例子,有同事写过一个嵌套了三层子查询的统计SQL,我让他先判定最内层子查询能否单独物化、外层条件能否下推。改完之后SQL变长了,但逻辑反而更清楚,执行时间从几十秒降到秒级。很多时候不是数据库优化器不行,而是SQL给了优化器太多干扰项。人肉做一遍“先判定”,往往能让优化器工作轻松一大截。

5.3 我踩过的坑与经验总结

实际接触这类框架后,我也踩过不少坑。第一个坑是过度相信判定结果。有个别查询因为索引统计信息严重过期,判定阶段把索引扫描判成了“可行且高效”,执行时因为大量回表反而更慢。所以判定规则虽然好用,但统计信息维护仍然不能松懈,结构信息能排除明显错误,但数据量变化带来的影响还得靠统计信息来兜底。

第二个坑是忽略了参数嗅探问题。绑定变量场景下,判定阶段的结构信息稳定,但精算阶段对具体参数值的感知仍然有限,偶尔会遇到和常量查询完全不同的执行计划。处理方式是对关键大查询拆分参数、对倾斜分布建自定义统计,而不是完全交给框架自动决定。第三个坑是升级优化器之后,有些原本依赖旧规则的手工SQL提示不再生效,需要重新梳理。

结合这些经验,我给同行的建议是:把“先判定后评估”当作优化器的辅助手段,而不是把所有责任丢给它。执行计划仍然是最终裁决者,DBA要保留解释计划、干预计划的能力。多用EXPLAIN、多观察执行统计、定期核对统计信息新鲜度,这三件事在任何时代都不会过时。

最后说一下我个人的体会。这几年国产数据库的优化器进步很快,但很多产品仍然热衷于把代价模型做得越来越细,却忽略了“先判定”这种更省力气的方向。金仓这套框架真正触动我的地方,是它把“不值得做的精确工作”和“必须做的精确工作”分清了。我们做系统设计、做SQL优化,其实也一直在面对同样的选择:资源有限,总得先想清楚哪些问题根本不值得投入精力。把精力节省下来,集中处理真正关键的部分,这个道理放在数据库优化器里成立,放在日常开发里同样成立。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询