PostgreSQL 优化器代码概览
简介
PostgreSQL 的开发源自上世纪80年代,它最初是 Michael Stonebraker 等人在美国国防部支持下创建的POSTGRE项目。上世纪末,Andrew Yu 等人在它上面搭建了第一个SQL Parser,这个版本称为Postgre95,也是加州大学伯克利分校版本的PostgreSQL的基石[1]。 我们今天看到的 PostgreSQL 的优化器代码主要是 Tom Lane 在过去的20年间贡献的,令人惊讶的是这20年的改动都是持续一以贯之的,Tom Lane 本人也无愧于“开源软件十大杰出贡献者”的称号。 但是从今天的视角,PostgreSQL 优化器不是一个好的实现,它用C语言实现,所以扩展性不好;它不是 Volcano 优化模型的[2],所以灵活性不好;它的很多优化复杂度很高(例如Join重排是System R[3]风格的动态规划算法),所以性能不好。 无论如何,PostgreSQL 是优化器的优秀实现和创新源头(想象 Greenplum 和 ORCA 等一系列项目),它的一些优化手段和代码结构在今天仍然是值得借鉴的,包括: 参数化路径,作用于indexed lookup join 术语解释 因为优化的单元(RelOptInfo)是子查询,合并子查询可以简化优化流程。关联的过程包括: pull_up_sublinks: 将可转换的 ANY/EXISTS 子句转换为 (anti-)semi-join 。一些优化器称这个过程为de-correlation。 Equivalence Class(EC)是 qual 的术语,它指代的是 expression 的等价性。例如,expression a = b AND b = c a = b AND b = 5 EC是很关键的数据结构,它的应用场景包括: 在 Join 时,EC用来决定 Join Key,它决定了 Outer Join 简化和PathKey设定 {a,c} 生成 EC 的入口是 generate_base_implied_equalities ,它从 query_planner 调入。也就是说,EC是在规划Join的前一刻生成的,这个阶段解析EC的代价最小,但是也决定了EC只能应用于Join优化。 Join重排 (有关Join重排的背景知识可以参考我之前的文章 SQL优化器原理 - Join重排) make_rel_from_joinlist是Join重排的入口,当前版本的 PostgreSQL 有三种算法: 你可以插入一个自定义的Join重排算法 geqo = on 现在让我们检查 Standard 算法。它的主入口在 join_search_one_level ,每次在已生成的局部计划的基础上: 按EC检查未加入的Join input,加入到生成的局部计划,这个操作仅产生 Left-deep-tree Standard 算法仍然是一个穷举的动态规划算法 如上所述,优化过程集中在对子查询(RelOptInfo)的重建过程,这可以理解为逻辑优化过程,这通常是跨关系代数操作符的、比较复杂的优化。事实上 PostgreSQL 也同步在做物理优化。 物理优化就是将 Path 加入 RelOptInfo。考虑Join,物理优化的入口在 populate_joinrel_with_paths。对每个JoinRel(Join RelOptInfo),考虑: sort_inner_and_outer:两边排序的MergeJoin路径 路径生成的入口是add_path,每次生成路径,需要更新RelOptInfo的最佳路径和最小代价以便后续动态规划选择全局最优。 流程图 planner 首先,PostgreSQL 的优化器代码可以说非常复杂,这已经极大限制了它的扩展性和灵活性。如果看一眼这部分代码的更新日志,会发现里面的作者已经只有少数几个人。 一部分扩展性限制是由编程语言带来的,因为C语言本身不容易扩展,这意味着大部分时候想要添加一个新的Node或Path变得很不容易,你需要定义一系列的数据结构、Cardinality Estimation逻辑、并行逻辑和Path解释逻辑。并没有类似interface这样的抽象指导你该怎么做。虽然,PostgreSQL 的代码已经写得非常工整,而且也有很多的文章告诉你该怎么做(比如 Introduction to Hacking PostgreSQL 和 The Internals of PostgreSQL)。 另一部分扩展性限制是优化器本身的结构带来的。现代的优化器基本都是Volcano Model[2]的(例如SQL Server和Oracle,就像他们声称的那样),而 PostgreSQL 没有实现为 Volcano Model 这种 Generic purpose,pluggable 的形式。影响包括: 无法做逻辑和物理优化的互操作。例如前文说到的,一个Join产生的EC必须和它紧跟的 RTE 结合才能产生 IndexedLookupJoin,而不像其他优化器可以把这个 EC (它在某种意义上已经是物理计划)下推到合适的逻辑计划上,指导它做物理计划转换。 可定制的Join重排算法 性能 虽然没有实验,但是 PostgreSQL 在优化上的性能可以想像是比较好的,这很大程度是用灵活×××换来的。 首先,不像 Volcano Optimizer ,PostgreSQL 优化器不需要不断生成中间节点,它的 RelOptInfo 的数量是相对稳定的(约等于Join的数量)。它的最优计划搜索以 RelOptInfo 为单位,如果 Join 重排不产生大量 RelOptInfo ,搜索宽度很低。 其次,RelOptInfo 简化了大量跨 Relational Expression 优化的细节,比起 Calcite 这种按 Relational Expression 来组织等价路径集合的方案, 它的搜索宽度进一步降低了。从等价集合的数量看, PostgreSQL 的搜索宽度大概比 Calcite 要低一个数量级,当然,如上所述,这是用更多优化可能性作为交换的。 最后,PostgreSQL 在优化阶段糅合了很多业务逻辑,在提高代码阅读的难度同时,也相应加快的优化效率。在优化过程中,PostgreSQL会不间断地做常量折叠、PathKey去重、Union打平、子查询打平……这些操作不会应用在memo里。 对比 Calcite/Orca ,PostgreSQL 的优化更快,更适合事务性场景。不过我无法判断 Calcite/Orca 在做了适当的剪枝和优化规则糅合后,是否也能支持事务场景。 注释 [2] Graefe,G.,& McKenna,W. J. (1993). The Volcano Optimizer Generator: Extensibility and Efficient Search. Proceedings of the Ninth International Conference on Data Engineering,(April),209–218. https://doi.org/10.1109/ICDE.1993.344061 [3] Selinger,P. Griffiths,et al. "Access path selection in a relational database management system." Proceedings of the 1979 ACM SIGMOD international conference on Management of data. ACM,1979. [4] Steinbrunn,M.,Moerkotte,& Kemper,A. (n.d.). Optimizing Join Orders,1–55. (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |