作者:徒南
优化器技术被公认为数据库领域中最有挑战性的技术之一,同时也是对数据库性能影响最大的一个模块。优化器直接影响SQL具体如何运行的执行计划,好的执行计划可以在毫秒内完成计算,而坏的执行计划则可能是分钟级或小时级别,两者性能可以相差成千上百倍。这篇文章将会为大家介绍PolarDB-X优化器的技术选型理由、技术架构与核心特性,帮助大家更深入地了解PolarDB-X优化器。
从技术历史发展的角度看,优化器技术演进经历了大致四个阶段:
以INGRES、早期Oracle为代表的启发式优化技术,主要优化手段有过滤条件下推,列裁剪,之后再做基于Cardinality的Join顺序调整。纯启发式优点就是简单,缺点是当查询稍微复杂就会导致找不到好的执行计划,并且会依赖一些很trick的魔数来调优。
以System-R、早期IBM DB2、以及绝大多数开源数据库(MySQL, PostgreSQL)为代表的启发式+基于代价的Join Reorder优化技术。优点相比于纯启发式优化,优化器针对(复杂查询)Join顺序,会基于代价利用自底向上的动态规划选择最优的Join顺序。缺点是搜索空间比较受限不一定能找到最优的执行计划,代价模型中需要显式考虑像排序的物理属性。
以IBM STARBURST、DB2、Oracle为代表的基于转换规则及代价的优化器技术。相比于阶段2,除了考虑代价,这类优化器已经抽象出作用于关系代数算子的转换规则(可以通过DSL编写)。优点是从工程角度上更易理解,维护,测试覆盖。缺点是优化规则会存在复杂的依赖关系,应用顺序需要人为指定。
以 Volcano/Cascades、SQL Server为代表的基于规则转换及代价的自顶向下动态规划优化技术(Volcano/Cascades模型)。相比于阶段3,它将优化的搜索过程做成了统一的框架,添加新的优化只需要关注优化规则本身,不需要关注规则的应用顺序,具有很高的扩展性。同时自顶向下的搜索具有一个很吸引人的特性就是搜索空间的剪枝,剪枝可以保证执行计划最优性的情况下减少搜索空间的搜索,提升优化效率。另外优化器作为一个数据库领域中很复杂的模块,任何的改动都可能涉及大量SQL的性能变化,从软件工程的角度,借助Volcano/Cascades模型的模块化及扩展性,优化器的性能可以被持续地调优和改进。
正是基于上面的考虑,PolarDB-X优化器被设计成一款以Volcano/Cascades模型作为框架的基于代价的优化器,它可以为每一条SQL构造出搜索空间,并根据数据的统计信息,基数估计,算子代价模型为搜索空间中的执行机计划估算出执行所需要的代价(CPU/MEM/IO/NET),最终选出代价最小的执行计划作为SQL的具体执行方式。我们知道PolarDB-X作为一款云原生分布式数据库,具有在线事务及分析的处理能力(HTAP)、计算存储分离、全局二级索引等重要特性,PolarDB-X优化器在这些特性中扮演了非常核心的角色。
优化器架构
优化器接受到SQL后会将它解析、转换成由关系代数算子组成的逻辑执行计划。整个PolarDB-X优化器中的具体优化手段都通过转换规则(Transformation Rule)来表达,转换规则会匹配特定结构的关系代数算子并将其转成等价的算子。
PolarDB-X的优化器的优化阶段主要分为三个:
Query Rewriter,基于RBO的启发式优化阶段,处理传统的逻辑优化如:子查询去关联化,列裁剪,谓词推导与下推,启发式Join Reorder(超多张表)等。这个优化阶段的特性是不断启发式地对同一个执行计划做逻辑优化,优化通常很快,且只做收益非常明确的优化器。
Plan Enumerator,基于CBO的Volcano/Cascades模型,是最为核心优化阶段,它会应用转换规则为计划生成搜索空间,既包含了逻辑优化如:Join Reorder(Bushy,Zig-Zag,Left Deep空间动态选择),算子交换,计算下推等,也包含物理优化如:全局二级索引选择,物理算法选择等。搜索空间被完全展开并搜索过后,每个物理执行计划都会根据具体的物理算子估算出执行所需要的代价(通过CPU/MEM/IO/NET表示)。最后代价最低的物理执行计划将会被选择出来。在整个优化过程中,这一步耗时占比最高,因为它需要考虑整个搜索空间。
MPP Planner,多机并行计算优化阶段,处理并行算子生成,算子间Shuffle的选择与消除,RunTimeFilter的生成及下推等。这一优化阶段专门用于优化OLAP的查询,保证可以充分利用多个节点的计算资源。
此外还有统计信息、代价模型、基数估计(Cardinality Estimation)等重要模块,好的优化效果依赖于准确的数据统计信息,PolarDB-X维护了丰富的统计信息用于辅助优化器,我们会为每张表维护行数,直方图,列长度,NDV值等统计信息。PolarDB-X的代价模型充分考虑了计算存储分离架构下的算子执行代价,与传统数据库相比会更精细地考虑网络的代价。
核心特性
HTAP
在HTAP混合负载处理方面,PolarDB-X提供智能路由的能力。我们知道传统的OLTP和OLAP的解决方案是基于简单的读写分离或者ETL模型,它们存在存储成本高、实时性差、链路和维护成本高等劣势。通过PolarDB-X可以统一处理HTAP负载,保证TP事务低延迟,同时保证AP分析查询充分利用计算资源,且保证数据的强一致。优化器在HTAP的负载识别中起了关键的作用。优化器会基于代价分析出查询的CPU,内存,IO,网络等核心资源消耗量,将请求区分为OLTP与OLAP请求。OLTP请求被路由至主副本执行, 相比于传统的读写分离方案能够提供更低的延迟。而分析出的OLAP请求将会通过MPP并行优化阶段,生成多机分布式的执行计划,下发至只读计算集群计算,访问只读副本,提供物理隔离,同时可以利用只读副本一致性读能力,保证强一致读。通过智能路由,用户可以非常透明地使用PolarDB-X同时处理OLTP及OLAP的诉求。
计算下推
PolarDB-X支持Partition Aware的计算下推。我们知道在计算与存储分离的架构下,我们获得了几乎无限弹性扩展计算节点的scale out能力,但代价是计算与存储间的网络交互开销。为了尽可能避免这一开销,可以通过计算下推,减少网络交互,计算离数据更近,计算效率获得提升,因此计算下推成了非常重要的优化手段。PolarDB-X用户大量使用拆分表(及广播表),将数据根据拆分方式打散至不同的分片上。PolarDB-X可以基于代价充分考虑存储(如存储的计算模型)及数据(是否具有索引)等特性,将查询中的部分计算(如:Join,Agg,Sort)下推至存储层进行计算。以TP场景下的Join的下推为例:如果Join不下推,我们会面临一个网络Lookup,通过网络Lookup的性能会劣于本地的磁盘Lookup,而通过计算下推我们就可以获得弹性扩容的同时,享受单机数据库的性能体验。
全局二级索引选择
PolarDB-X为用户提供全局二级索引,并提供数据强一致性。在建立全局二级索引后,优化器可以立马感知到表存在全局二级索引,并为查询优化出更优的查询计划。以订单为例子,用户将订单表按照买家维度作拆分,以买家维度作为查询可以获得非常好的性能。但按卖家维度进行查询时,需要将所有数据分片查询一遍才能得到完整结果。而通过为订单表建立卖家维度的全局二级索引,优化器就可以优化出访问全局二级索引(回表)的执行计划,避免全分片扫描,提升性能。另外,全局二级索引支持覆盖索引,优化器结合列裁剪优化,当发现用户查询表的列被全局二级索引覆盖时,可以做到只访问全局二级索引,避免回表。全局二级索引还可以和Partition Aware的计算下推做共同优化,例如:两张表的Join的场景,两表的拆分方式不一致导致Join无法下推的时候,我们可以将表按照Join Key共同的维度建立全局二级索引,达到计算下推的目的。
优化例子
下面让我们一起通过一个TPCH Q3作为例子看看PolarDB-X如何优化一条SQL吧。首先TPCH Q3这条SQL会被转化为如下的逻辑执行计划(LogicalPlan)
这个逻辑执行计划包含Agg、Join、Filter、Project、LogicalView等关系代数算子,PolarDB-X通过LogicalView算子来抽象下发至存储执行的Plan(所以下推算子在优化中就是将算子下推进LogicalVIew算子)。
接着我们一步一步看一下RBO,CBO,MPP三个优化阶段分别做了哪些优化。
RBO启发式阶段,可以看到经过RBO后的逻辑执行计划已经将Filter,Project等算子下推到了LogicalVIew,也就是完成了列裁剪,谓词下推等优化。同时我们可以看到原来Orders,Lineitem表也因为拥有相同拆分规则被下推到了同一个LogicalVIew,完成了计算的下推优化。
CBO基于代价的优化阶段,可以看到逻辑执行计划变成了物理执行计划,其中Join和Agg分表选择使用HashJoin和HashAgg作为物理算子。在这背后,优化器实际上已经考虑了不同的Join顺序,不同的Join与Agg算法,被选择出来的就是具有最低代价的物理执行计划。
MPP分布式优化阶段,可以看到在算子间多了Exchange算子,用于描述数据如何在多个计算节点间进行Shuffle。这使得执行器可以知道如何在多个计算节点中传输数据,并行计算。此外,还可以看到RunTimeFilter生成,并下推到存储节点执行提前过滤数据,减少网络传输开销。
总结
PolarDB-X 查询优化器主要基于 Volcano/Cascades 模型设计,是一个基于代价的优化器(CBO),优化过程主要分为查询改写、计划枚举、MPP 优化三个阶段。其中,查询改写阶段负责启发式优化规则(例如查询下推);计划枚举阶段负责索引选择和决定 Join Order 等,是最核心的阶段;如果优化器判断执行计划代价较高、需要 MPP 执行,则会通过 MPP 优化阶段将执行计划进一步优化为分布式执行计划。通过这样的设计,让 PolarDB-X 能很好的适应 HTAP 场景,兼顾 TP 与 AP 两种不同模式的流量。