一、概述

经过 Analyzer 的处理,Unresolved LogicalPlan 解析为 Analyzed LogicalPlan,在实际应用中,很多低效的写法会带来执行效率的问题,需要进一步对 Analyzed LogicalPlan 进行优化处理,得到优化后的逻辑算子树。

二、实现

2.1. RuleExecutor

Optimizer 继承自 RuleExecutor 类,本身没有重载 RuleExecutor 中的 $execute()$ 方法,因此其执行过程仍然是调用其父类 RuleExecutor 中实现的 execute 方法。在 QueryExecution 中,Optimizer 会对传入的 Analyzed LogicalPlan 执行 $execute()$ 方法,启动优化过程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def executeAndCheck(plan: LogicalPlan, tracker: QueryPlanningTracker): LogicalPlan = {
if (plan.analyzed) return plan
AnalysisHelper.markInAnalyzer {
val analyzed = executeAndTrack(plan, tracker)
try {
checkAnalysis(analyzed)
analyzed
} catch {
case e: AnalysisException =>
val ae = e.copy(plan = Option(analyzed))
ae.setStackTrace(e.getStackTrace)
throw ae
}
}
}

$withCachedData.clone$

optimization 阶段首先要去CacheManager保存的缓存里面查一查有没有已缓存的查询计划,有的话复用就可以了,没必要再跑一遍。

1
2
3
4
5
6
7
8
lazy val withCachedData: LogicalPlan = sparkSession.withActive {
// 用来检测逻辑计划是否已经经过 analysis 阶段
assertAnalyzed()
// 用来检测流数据查询的
assertSupported()
// clone 避免在不同的阶段比如: analyzing/optimizing/planning 之间共享计划实例
sparkSession.sharedState.cacheManager.useCachedData(commandExecuted.clone())
}

2.2. 规则批次

与 Analyzer 类似,Optimizer 的主要机制也依赖重新定义的一系列规则,同样对应 RuleExecutor 类中的成员变量 batches,因此在 RuleExecutor 执行 $execute()$ 方法时会直接利用这些规则 Batch。

1
2
/** Defines a sequence of rule batches, to be overridden by the implementation. */
protected def batches: Seq[Batch]

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

  1. InlineCTE

2.2.4. Aggregate