PostgreSQL 的开发源自上世纪80年代,它最初是 Michael Stonebraker 等人在美国国防部支持下建立的POSTGRE项目。上世纪末,Andrew Yu 等人在它上面搭建了第一个SQL Parser,这个版本称为Postgre95,也是加州大学伯克利分校版本的PostgreSQL的基石[1]。html
咱们今天看到的 PostgreSQL 的优化器代码主要是 Tom Lane 在过去的20年间贡献的,使人惊讶的是这20年的改动都是持续一以贯之的,Tom Lane 本人也无愧于“开源软件十大杰出贡献者”的称号。git
可是从今天的视角,PostgreSQL 优化器不是一个好的实现,它用C语言实现,因此扩展性很差;它不是 Volcano 优化模型的[2],因此灵活性很差;它的不少优化复杂度很高(例如Join重排是System R[3]风格的动态规划算法),因此性能很差。github
不管如何,PostgreSQL 是优化器的优秀实现和创新源头(想象 Greenplum 和 ORCA 等一系列项目),它的一些优化手段和代码结构在今天仍然是值得借鉴的,包括:算法
本文尝试快速地浏览一遍 PostgreSQL 优化器的代码,和现代优化器比较优缺点。大部分的 PostgreSQL 优化器代码来自于 github.com/postgres/po… 。 咱们提到现代优化器主要指的是 Apache Calcite 和 ORCA。sql
子查询上拉数据库
由于优化的单元(RelOptInfo)是子查询,合并子查询能够简化优化流程。关联的过程包括:express
EquivalenceClass解析编程
Equivalence Class(EC)是 qual 的术语,它指代的是 expression 的等价性。例如,expressionbash
a = b AND b = c复制代码
则称 {a, b, c} 是一个EC。特别地,在 PostgreSQL 中,expression数据结构
a = b AND b = 5复制代码
只生成简化的EC:{a = 5} {b = 5}
EC是很关键的数据结构,它的应用场景包括:
EC是一个树形结构,每一个节点是一个EC,并连接到它合并的父节点上。考虑a = b AND b = c
的例子,最后的EC tree表达为
{a, b, c}
|- {a, b}
|- {b, c} 复制代码
其中,每一个EC内部的expression称为EquivalenceMember(EM)。
生成 EC 的入口是 generate_base_implied_equalities ,它从 query_planner 调入。也就是说,EC是在规划Join的前一刻生成的,这个阶段解析EC的代价最小,可是也决定了EC只能应用于Join优化。
Join重排
(有关Join重排的背景知识能够参考我以前的文章 SQL优化器原理 - Join重排)
make_rel_from_joinlist是Join重排的入口,当前版本的 PostgreSQL 有三种算法:
默认在12路及以上的复杂Join中会打开GEQO。能够在postgresql.conf中修改参数
geqo = on
geqo_threshold = 12复制代码
控制GEQO设定。
如今让咱们检查 Standard 算法。它的主入口在 join_search_one_level ,每次在已生成的局部计划的基础上:
上述描述已经足够复杂,让咱们总结一下 Standard 算法:
路径生成和动态规划
如上所述,优化过程集中在对子查询(RelOptInfo)的重建过程,这能够理解为逻辑优化过程,这一般是跨关系代数操做符的、比较复杂的优化。事实上 PostgreSQL 也同步在作物理优化。
物理优化就是将 Path 加入 RelOptInfo。考虑Join,物理优化的入口在 populate_joinrel_with_paths。对每一个JoinRel(Join RelOptInfo),考虑:
有趣的点是HashJoin路径(hash_inner_and_outer),顾名思义,它要求Join两边都计算哈希值。在生成Path过程当中,须要计算两边的参数信息。例如A join B on A.x = B.y
,对于A来讲,x是参数,对于B是y。若是选定A做为Probe side,一旦B上有y的索引,每次x的probe将以参数的形式传递给y的索引。经过调用 get_joinrel_parampathinfo 来产生参数信息。
路径生成的入口是add_path,每次生成路径,须要更新RelOptInfo的最佳路径和最小代价以便后续动态规划选择全局最优。
流程图
planner
|- subquery_planner 迭代的子查询优化
|- pull_up_sublinks de-correlation
|- pull_up_subqueries 子查询上拉
|- preprocess_expression Query/PlannerInfo 结构解析,常量折叠
|- remove_useless_groupby_columns
|- reduce_outer_joins Outer Join退化
|- grouping_planner
|- plan_set_operations SetOp优化
|- query_planner 子查询优化主入口
|- generate_base_implied_equalities 生成/合并EC
|- make_one_rel Join优化入口
|- set_base_rel_pathlists 生成Join RelOptInfo列表
|- make_rel_from_joinlist Join重排和规划
|- standard_join_search 标准Join重排算法
|- join_search_one_level
|- make_join_rel 生成JoinRel和对应的Path
|- create_XXX_paths Grouping、window等其余expression优化复制代码
扩展性和灵活性
首先,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 的形式。影响包括:
PostgreSQL 仍然提供了部分手写的 Plugin Point,包括:
等等。
性能
虽然没有实验,可是 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 在作了适当的剪枝和优化规则糅合后,是否也能支持事务场景。
[1] Brief History of PostgreSQL, www.postgresql.org/docs/curren…
[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. doi.org/10.1109/ICD…
[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, G., & Kemper, A. (n.d.). Optimizing Join Orders, 1–55.