简介:本文为想系统学习数据库查询优化器的开发者梳理了一条清晰的学习路径:先夯实关系代数、代价模型与统计信息等基础,再逐步掌握经典优化框架、基于规则的查询改写、连接顺序枚举与分布式优化等核心主题,并给出论文阅读方法与动手实践建议,帮助你构建完整的优化器知识体系。
在数据库体系结构中,优化器处于承上启下的核心位置:它接收解析器产出的逻辑计划,结合统计信息与代价模型,输出最终可执行的物理计划。同一条 SQL,在不同连接顺序、不同访问路径、不同改写策略下,执行时间可能相差几个数量级。可以说,优化器的能力上限,很大程度上决定了数据库在复杂查询场景下的表现上限。
对开发者而言,系统学习优化器至少有三重价值:一是从事数据库内核、大数据计算引擎等方向的研发工作,优化器是绕不开的核心模块;二是做性能诊断与调优时,只有理解优化器的决策逻辑,才能真正看懂执行计划、定位慢查询根因;三是优化器融合了算法、统计、系统工程等多领域知识,是训练系统设计能力的绝佳素材。
在深入细节之前,建议先在脑海中建立优化器的完整工作流水线:
理解了这条流水线,再去读论文或源码时,就能迅速定位每个技术点在整体中的位置,避免“只见树木不见森林”。
优化器的所有决策都建立在两块基石之上:关系代数与代价估算。
关系代数层面,需要熟练掌握扫描、选择、投影、连接、聚合等算子的语义与等价变换规则,理解“逻辑计划”与“物理计划”的分层思想:前者描述“做什么”,后者描述“怎么做”。
代价估算层面,需要理解三个关键概念:
入门阅读方面,可以从上世纪七十年代末那篇奠定“基于代价的访问路径选择”思想的经典论文开始——它提出的自底向上动态规划枚举框架,至今仍是许多数据库优化器的骨架。随后可再精读一篇关系系统查询优化综述,快速补齐全景认知。
学术界与工业界经过数十年演进,形成了两类主流搜索框架,理解其差异是进阶的关键。
1. 自底向上的动态规划框架。 思路是:先为单表选择最优访问路径,再自小到大枚举连接集合,为每个子集合保留最优计划,最终合成整体最优计划。这类方法实现简单、行为可预测,在连接数不多时效果很好;但对复杂查询的搜索空间控制能力有限。
2. 自顶向下基于 Memo 的框架。 思路是:将所有等价的逻辑与物理表达式组织在一个记忆化结构中,等价表达式组共享存储;搜索自顶向下展开,利用分支限界(当前累计代价已超过已知最优即剪枝)高效收缩空间。这类框架天然支持规则的可扩展性——新增改写规则只需注册,无需改动搜索主体,因此被许多现代分布式数据库采用。
上世纪八十年代末到九十年代中期,有一系列论文系统阐述了“用类文法的函数式规则表达优化备选方案”“可扩展优化器生成器”“自顶向下搜索框架”等思想,是理解现代优化器架构的必读材料。阅读时建议重点体会:规则如何统一表达逻辑等价与物理实现、记忆化结构如何避免重复推导、剪枝如何与代价模型联动。
如果说搜索框架是优化器的“骨架”,查询改写就是“肌肉”。这一主题在论文体系中占比极高,也是日常调优中最能直接受益的部分。核心改写包括:
1. 谓词移动与下推:将过滤条件尽量推向数据源,尽早缩小中间结果:
-- 改写前:先连接后过滤SELECT t1.a, t2.bFROM t1 JOIN t2 ON t1.id = t2.idWHERE t1.dt = '2024-01-01';-- 优化器改写后(示意):t1 先按 dt 过滤再参与连接,-- 连接输入规模大幅缩小,代价显著下降
2. 子查询去关联与消除:将嵌套子查询改写为等价的连接或窗口计算,避免“逐行执行子查询”的灾难计划:
-- 原始写法:相关子查询SELECT e.nameFROM emp eWHERE e.sal > (SELECT AVG(sal) FROM emp WHERE dept_id = e.dept_id);-- 去关联改写(示意):一次窗口计算替代逐行子查询SELECT name FROM (SELECT name, sal,AVG(sal) OVER (PARTITION BY dept_id) AS avg_salFROM emp) tWHERE sal > avg_sal;
围绕这一主题,从九十年代的谓词移动、提前聚合与延迟聚合,到本世纪初的参数化查询嵌套等价、子查询与聚合的正交优化、利用窗口聚合消除子查询,再到近年“任意查询的完整去关联”,构成了一条清晰的演进脉络,建议按时间线依次精读。
3. 聚合变换:根据数据分布与分组基数,选择提前聚合(先局部聚合以减少连接输入)或延迟聚合(避免过早聚合丢失信息)。
4. 基于代价的改写:并非所有改写都无条件有益,成熟实现普遍将改写规则纳入代价体系,由“改写后估算代价是否更优”决定是否生效,而非机械套用。
连接顺序是搜索空间爆炸的主要来源:n 个表的连接顺序数量随 n 阶乘级增长,而连接顺序选择本身是 NP-hard 问题。需要掌握:
动手实现一个支持三到四表连接的动态规划枚举器,是检验学习效果的最佳练习:
# 连接顺序动态规划(示意)best[单表集合] = 该表最优访问路径for size in 2..n:for 每个大小为 size 的子集 S:for S 的每种拆分 (S1, S2):for p1 in best[S1], p2 in best[S2]:c = cost(join(p1, p2))best[S] = min(best[S], c)return best[全表集合]
当查询跨越多节点,优化器需要额外决策:数据如何在节点间重分布、连接在哪一侧执行、聚合是否分阶段进行。此时代价模型需引入网络传输与并行度因素,“以计算换传输”还是“以传输换计算”成为核心权衡。近年一些面向大数据与实时分析场景的论文提供了很好的参考:前者提出了模块化的优化器架构,将优化逻辑、中间表示与执行引擎适配解耦,便于跨引擎复用;后者完整呈现了一个现代分布式优化器从表达式改写到分布式计划生成的全流程。此外,参数化查询的计划缓存与重优化、计划在多变负载下的稳定性,也是工程落地必须面对的课题。
优化器本身是“生成计划”的软件,其质量同样需要工程化保障,值得关注的方向包括:
结合以上路线,给出三条可操作的建议:
学习查询优化器,本质上是理解“数据库如何在巨大的搜索空间中,基于不完美的信息做出尽可能好的决策”。建议的路径是:先以关系代数与代价模型打底,再吃透两大搜索框架,随后沿时间线精读查询改写与连接顺序优化的经典论文,最后进入分布式架构与质量保障的工程视角,并始终用实验与源码阅读闭环验证理解。坚持下来,你不仅能看懂执行计划背后的逻辑,也具备了参与优化器研发与深度调优的扎实能力。