Bootstrap

SparkSQL 对 SQL 查询的优化静态优化和动态优化两大部分介绍

SparkSQL 对 SQL 查询的优化主要分为 静态优化动态优化 两大部分,其中静态优化主要在查询编译时进行,而动态优化则是在查询执行过程中进行。SparkSQL 的优化包括了多种技术,例如 RBO(基于规则的优化)CBO(基于成本的优化),以及 AQE(Adaptive Query Execution,适应性查询执行)。这些优化方法和技术可以显著提高查询的性能。

1. 静态优化(Static Optimization)

静态优化是在 SparkSQL 查询的解析和逻辑计划生成后,在物理计划生成前应用的优化。这些优化是在查询计划的编译阶段完成的,并不依赖于运行时数据特征。

主要的静态优化技术:
  • 基于规则的优化(RBO):规则驱动的优化方法,通过定义优化规则来改变查询的执行计划。
  • 基于成本的优化(CBO):通过对不同物理计划的成本进行评估,选择代价最小的执行计划。
RBO(基于规则的优化)

RBO 是 SparkSQL 默认的优化策略,基于一些预定义的规则对逻辑计划进行优化。规则通常包括:

  • 合并相邻的 Filter 操作
  • 常量折叠:将常量表达式提前计算。
  • 子查询合并:优化子查询,使其转化为连接操作。
  • 表达式简化:对不必要的运算进行简化。

这些规则定义在 SparkSQL 的 Optimizer 类中,通常是通过继承 Rule[LogicalPlan] 类来定义自定义规则。

示例:合并连续的 Filter 操作。

object MergeFilters extends Rule[LogicalPlan] {
  def apply(plan: LogicalPlan): LogicalPlan = plan match {
    case Filter(condition1, Filter(condition2, child)) =>
      // 合并条件,避免多次扫描数据
      Filter(condition1 && condition2, child)
    case _ => plan
  }
}

这段代码演示了如何合并两个连续的 Filter 节点。假设我们有一个查询:

SELECT * FROM employees WHERE age > 30 AND age < 50;

在没有优化规则时,Catalyst 会生成两个 Filter 节点:

Filter(age > 30)
  Filter(age < 50)
    Scan(employees)

通过 RBO 规则优化后,可以合并为:

Filter(age > 30 AND age < 50)
  Scan(employees)

这样,执行时就只需要扫描一次数据。

CBO(基于成本的优化)

CBO 通过计算不同物理计划的代价来选择最优的执行计划。SparkSQL 会生成多个物理执行计划,然后使用一个启发式的代价模型来选择执行计划。代价模型通常包括以下几个因素:

  • 执行时间。
  • I/O 成本。
  • 内存使用等。

CBO 主要是在物理计划生成阶段进行的,因此它比 RBO 更加依赖于数据的实际特征,如数据的大小、分布等。

示例:选择 HashJoin 或 SortMergeJoin。

在某些情况下,SparkSQL 可能会选择使用 HashJoinSortMergeJoin,具体取决于数据的大小和分布。例如,如果两个表非常大,可能会选择 SortMergeJoin,因为它更适合处理大数据集,而对于较小的数据集,HashJoin 会更高效。

CBO 会基于数据统计信息(如数据的大小、列的分布等)来做出决策。

2. 动态优化(Dynamic Optimization)

动态优化是 Spark 在查询执行过程中根据实际的数据分布情况,调整执行计划的能力。最主要的动态优化技术是 AQE(Adaptive Query Execution,适应性查询执行)

AQE(适应性查询执行)

AQE 是 Spark 3.0 引入的一个新特性,它可以根据运行时的统计信息(例如,分区的大小和数据分布)动态调整查询的执行计划。AQE 会在查询执行过程中根据数据的实际分布,调整物理计划,解决静态优化时未能考虑到的性能瓶颈。

AQE 的核心优化技术:
  • 动态调整 Shuffle 分区数:在执行查询时,AQE 会根据每个阶段的数据量,动态调整 Shuffle 的分区数。这样可以避免过多的 Shuffle 分区导致性能下降。
  • 动态选择 Join 策略:在执行过程中,AQE 会动态选择最优的 Join 策略(例如,决定使用 HashJoin 或 SortMergeJoin)。
  • 处理 Skew Join:在某些情况下,数据分布不均可能导致某个分区的数据量过大(数据倾斜)。AQE 可以动态调整处理策略,避免某个节点超载。

AQE 工作流程:

  1. 生成初始物理计划:Spark 在静态优化后生成一个初步的物理执行计划。
  2. 执行并收集统计信息:在执行过程中,Spark 会动态地收集执行过程中每个阶段的统计信息(例如,分区的大小)。
  3. 重新优化:根据收集到的统计信息,Spark 会重新调整物理执行计划。

示例:动态调整 Shuffle 分区数。

spark.conf.set("spark.sql.adaptive.enabled", "true")
spark.conf.set("spark.sql.adaptive.shuffle.targetPostShuffleInputSize", "134217728")

在启用 AQE 后,Spark 会根据每个阶段的数据量动态调整 Shuffle 分区数,确保每个分区的数据量接近设定的目标(例如上面的 128MB)。

3. 总结:

  • 静态优化:包括 RBO 和 CBO,主要在查询编译阶段应用。RBO 使用预定义的规则进行优化,而 CBO 通过评估不同计划的成本来选择最佳计划。
  • 动态优化(AQE):在查询执行过程中,根据运行时的统计信息动态调整执行计划,解决静态优化时无法预测的性能瓶颈问题。
    • RBO:通过一系列的优化规则对逻辑计划进行优化。
    • CBO:通过评估不同物理计划的代价,选择最优的执行计划。
    • AQE:在运行时动态调整执行计划,处理 Shuffle 分区调整、Join 策略选择、Skew Join 等问题。

通过这些优化方法,SparkSQL 能够在保证正确性的基础上显著提高查询的性能。

;