Spark-源码学习-SparkSQL-架构设计-SQL 引擎-Optimizer 模块
一、概述
经过 Analyzer 的处理,Unresolved LogicalPlan 解析为 Analyzed LogicalPlan,在实际应用中,很多低效的写法会带来执行效率的问题,需要进一步对 Analyzed LogicalPlan 进行优化处理,得到优化后的逻辑算子树。
二、实现
2.1. RuleExecutor
Optimizer 继承自 RuleExecutor 类,本身没有重载 RuleExecutor 中的 $execute()$ 方法,因此其执行过程仍然是调用其父类 RuleExecutor 中实现的 execute 方法。在 QueryExecution 中,Optimizer 会对传入的 Analyzed LogicalPlan 执行 $execute()$ 方法,启动优化过程。
1 | def executeAndCheck(plan: LogicalPlan, tracker: QueryPlanningTracker): LogicalPlan = { |
$withCachedData.clone$
optimization
阶段首先要去CacheManager
保存的缓存里面查一查有没有已缓存的查询计划,有的话复用就可以了,没必要再跑一遍。
1 | lazy val withCachedData: LogicalPlan = sparkSession.withActive { |
2.2. 规则批次
与 Analyzer 类似,Optimizer 的主要机制也依赖重新定义的一系列规则,同样对应 RuleExecutor 类中的成员变量 batches
,因此在 RuleExecutor 执行 $execute()$ 方法时会直接利用这些规则 Batch。
1 | /** Defines a sequence of rule batches, to be overridden by the implementation. */ |
2.2.1. Eliminate Distinct
2.2.2. FinishAnalysis
严格来讲,Finish Analysis 这个 Batch 中的一些规则更多的是为了得到正确的结果(例 如 ComputeCurrentTime ),并不涉及优化操作, 从逻辑上更应该归于 Analyzer 的分析规则 中 。 但是考虑到 Analyzer 中会进行 一 些规范化的操作, 因此将 EliminateSubqueryAliases 和 ComputeCurrentTime 规则放在优化的部分, 实际上真正的优化过程从下一个 Batch 开始。
消除子查询别名,对应逻辑算子树中的 SubqueryAlias 节点。
一般来讲,Subqueries 仅用于提供查询的视角范围信息, 一旦 Analyzer 阶段结束, 该节点就可以被移除,该优化规则直接将 Subq时ryA!ias 替换为其子节点
表达式替换,在逻辑算子树中查找匹配 RuntimeReplaceable
的表达式,并将其替换为能够执行的正常表达式。 这条规则通常用来对其他类型的数据库提供兼容的能力。
例如,可以用
coalesce
来替换支持nvl
的表达式。
计算与当前时间相关的表达式,在同一条 SQL 语句中可能包含多个计算时间的表达式,即 CurrentDate 和 CurrentTimestamp ,且该表达式出现在多个语句中。为避免不一致,ComputeCurrentTime 对逻辑算子树中的时间函数计算一次后,将其他同样的函数替换成该计算结果。
重写 Distinct 聚合操作,对于包含 Distinct 算子的聚合语句, 这 条规则将其转换为两个常规的聚合表达式。
2.2.3. Inline CTE
- InlineCTE