首发于Spark SQL

Spark SQL深入分析之图解五种Join策略的执行流程与应用场景

前言

Join(连接)操作是大数据分析领域必不可少的操作,本文将从原理层面介绍SparkSQL支持的五大连接策略及其适用场景。通过本文的学习,你将会了解Spark SQL中五大连接策略的连接原理,并且学会根据不同的影响因素和不同的需求场景,选择合适的连接策略,从而更好地完成你的工作。五大连接策略

Spark SQL内置了五种连接策略,分别如下所示:

  1. Broadcast Hash Join
  2. Shuffle Hash Join
  3. Shuffle Sort Merge Join
  4. Cartesian Product Join
  5. Broadcast Nested Loop Join
这五种连接策略分别对应Spark SQL中五个物理操作符:

三大影响因素在处理实际需求时,可能会根据不同的场景选择不同连接策略,而选择不同的连接操作会得到不同的处理效率。一般情况下,有三个因素影响连接操作的效率,它们分别是:

  1. Join type is equi-join or not 连接类型是否为equi-join(等值连接)
  2. Join strategy hint 连接策略提示
  3. Size of Join relations 连接数据集的大小

本文剩下部分先解释这三个因素,然后讲解每个连接类型的特征与工作原理,了解这些之后你就可以基于这三个影响因素去分析,从而选择合适的连接策略去解决你的需求。下面对这三个影响因素进行一一说明。

1、是否等值连接

等值连接是一个在连接条件中只包含equals比较的连接,而非等值连接包含除equals以外的任何比较,例如<, >, >=, <=。由于非等值连接需要对不确定的值的范围进行比较,因而嵌套循环是必须的。因此,对于非等值连接,Spark SQL只支持Broadcast Nested Loop Join(广播嵌套循环连接)和Cartesian Product Join(笛卡尔乘积连接)。而所有连接运算符都支持等值连接。Spark SQL定义了ExtractEquiJoinKeys模式,JoinSelection(规划连接操作的核心对象)使用它来检查逻辑连接计划是否是等值连接。如果是等值连接,连接的元素将从逻辑计划中提取出来,包括连接类型、左键、右键、连接条件、左连接关系、右连接关系和连接提示。这些元素的信息构成了接下来连接规划过程的基础。

2、连接提示

Spark SQL为开发人员提供了通过连接提示对连接策略选择进行一些控制。在Spark 3.0.0版本中,支持以下四个连接提示:

开发人员可以在SELECT子句中添加带有提示类型和表名称的提示语法:

UnresolvedHint节点由解析器生成,并由分析器转换为ResolvedHint节点:

优化器应用了EliminateResolvedHint规则,从而将提示信息移入连接运算符,并删除ResolvedHint运算符。

连接提示信息是通过上述ExtractEquiJoinKeys模式提取的,并用于连接策略选择过程,这一点稍后将在本文中解释。

3、连接的数据集大小

选择连接策略最重要的因素是连接数据集的大小。选择连接策略的核心原则是避免reshuffle和重新排序的操作,这些操作非常昂贵,对查询的性能影响很大。因此,首选的连接策略是不需要reshuffle或者重新排序的连接策略,如基于hash的连接策略。然而,这些连接策略的可用性取决于连接中涉及的数据集的大小。如何选择合适的连接策略

上一小节我们已经了解在选择连接策略时需要考虑的三个因素,接下来将深入研究五大连接策略的原理以及在不同的影响因素下如何选择合适的连接策略。在Spark SQL的物理计划阶段,JoinSelection对象会根据是否等值连接、连接提示类型、连接的数据集大小以及参与连接的key是否可以排序等条件进行连接策略选取,基于JoinSelection对象中选择连接策略的逻辑,绘制以下流程图:

下文的分析都会围绕以上流程图展开,从图中可以看到,连接选取过程从判断是否为等值连接开始。

首先判断是否为等值连接

等值连接在连接操作中是最为为流行和使用的,Spark SQL的五大连接策略都支持等值连接。下面为截取以上流程图中已选择等值连接作为连接策略的部分流程图,我们可以看到,候选连接策略按照可能的连接性能顺序进行搜索,第一个符合选择条件的策略将被返回,选择流程结束,不再考虑其他策略。

根据给定的连接提示选择策略

在以上等值连接部分流程图我们也可以清楚看到,开发人员指定的连接提示对于连接策略的选择具有最高的优先权。当指定了一个有效的连接提示时,将根据以下条件选择连接策略:

  1. 对于BROADCAST提示,选择Broadcast Hash Join策略,当BROADCAST提示在连接的两边都被指定时,选择数据集较小的一边。
  2. 对于SUFFLE_HASH提示,选择Shuffle Hash Join策略,当SUFFLE_HASH提示在连接的两边都被指定时,选择数据集较小的一边。
  3. 对于SUFFLE_MERGE提示,如果连接键是可排序的,选择Shuffle Sort Merge Join策略。
  4. 对于SUFFLE_REPLICATE_NL,如果连接类型是内部连接,选择Cartesian Product Join策略。

如果开发人员没有指定连接提示,或者对于指定的连接提示不满足连接条件,选择流程将会往下继续,下面将会讨论参与连接的数据集大小的情况。

Broadcast Hash Join的满足条件与运行原理

选择流程继续往下移动,到了判断连接数据集的大小的节点,当连接数据集中至少有一方小到可以收集到driver端,然后广播到每个executor时,Broadcast Hash Join是首选策略。Broadcast Hash Join背后的思想是:在driver端将小规模的数据集广播给每个executor的成本低于reshuffle或者重新排序所需的成本。为了使Broadcast Hash Join具有良好的性能,需要广播的数据集必须足够小,否则,性能会变差,甚至最终出现内存不足的错误。使用广播的数据集的默认大小阈值是10MB,也就是说,数据集需要小于10MB。默认大小可以通过配置 spark.sql.autoBroadcastJoinThreshold的值来调整,该设置基于你的driver端和executor端的可用内存。

在内部,Broadcast Hash Join重写了requiredChildDistribution方法并声明了数据集的广播分发需求。

当在实际执行之前应用EnsureRequirements规则时,将在执行连接之前添加BroadcastExchange物理运算符:(原理可参考:Spark Catalyst QueryExecution总体概况中的Execution Preparation部分)

当BroadcastExchange操作符被执行时,它首先收集数据集的分区以广播给Driver:

如果数据集的总行数小于MAX_BROADCAST_TABLE_ROWS阈值,数据集将被广播,MAX_BROADCAST_TABLE_ROWS阈值被设置为3.41亿行,这是BytesToBytesMap最大能力容量的70%。注意,这个阈值与 spark.sql.autoBroadcastJoinThreshold(默认为10MB)不同。MAX_BROADCAST_TABLE_ROWS阈值用于控制广播交换时的广播操作,而spark.sql.autoBroadcastJoinThreshold用于控制Broadcast Hash Join策略的选择。

如果数据集的行数小于MAX_BROADCAST_TABLE_ROWS阈值,数据集的副本将以HashhedRelation类型的广播变量形式发送到每个executor:

在executor端,广播的数据集被用作连接的BuildTable,而最初存在于executor中的数据集,即连接的大表,被用作连接的StreamTable。连接过程遍历StreamTable,并在BuildTable中查找匹配的行。

Shuffle Hash Join的满足条件与运行原理

如果选择Broadcast Hash Join策略的条件没有得到满足,选择流程就会转向检查是否已经将Shuffle Sort Merge Join策略设置为首选。如果没有设置,就会检查是否满足使用Shuffle Hash Join策略的条件。

满足Shuffle Hash Join策略的条件是,至少有一个连接数据集需要小到足以建立一个hash table(使得较小的数据集能够加载到内存中),其大小应该小于广播阈值(spark.sql.autoBroadcastJoinThreshold)和shuffle分区数的乘积。

此外,较小的数据集需要比较大数据集至少小3倍,否则,基于排序的连接策略可能收益更大。

Shuffle Hash Join策略要求对连接的两个数据集进行shuffle,以便将两边数据集中具有相同连接键的行放在同一个executor中。为较小的数据集创建一个hashedRelation,并被用作连接的BuildTable。较大的数据集被用作StreamTable。

Shuffle Sort Merge Join的满足条件与运行原理

如果选择Shuffle Hash Join策略的条件没有得到满足,或者Shuffle Sort Merge Join策略被配置为首选,那么选择流程就会继续检查是否满足使用Shuffle Sort Merge Join策略的条件。为了使用基于排序的连接算法,连接键必须是可排序的。

Shuffle Sort Merge Join策略不需要将任何连接数据集装入内存(这一点与Shuffle Hash Join策略连接不同),所以连接数据集没有大小限制。尽管基于排序的连接算法通常没有基于hash的连接算法快,但它通常比嵌套循环的连接算法表现得更好。因此,基于性能和灵活性的双重考虑,Shuffle Sort Merge Join是一个折中的选择方案。Shuffle Sort Merge Join策略也需要对连接的两个数据集进行shuffle,以便将两边数据集中具有相同连接键的行放在同一个executor中。此外,每个分区的数据都需要按连接键进行升序排序。

这两个连接数据集中的任何一个都可以作为StreamTable或BuildTable使用。当一个数据集被用作连接的StreamTable时,它被按顺序逐行迭代。对于每个StreamTable行,BuildTable也是按顺序逐行搜索的,以找到与StreamTable行有相同连接键的行。由于StreamTable和BuildTable都是按连接键排序的,当连接过程转移到下一个StreamTable行时,BuildTable的搜索不必从第一个BuildTable行开始,而只需要从与最后一个StreamTable行相匹配的BuildTable行继续。

Cartesian Product Join的满足条件与运行原理

如果选择Shuffle Sort Merge Join策略的条件没有得到满足,并且连接的JoinType是InnerLike,流程则会选择Cartesian Product Join。这通常发生在没有定义连接条件的连接查询中。笛卡尔乘积连接的核心思想是计算两个连接数据集的乘积。因此可以想象,对于大型数据集,笛卡尔乘积连接的性能可能会非常糟糕,因此应该尽量避免这种类型的连接。

Broadcast Nested Loop Join的满足条件与运行原理

当以上四种连接策略的条件都不满足时,流程会选择Broadcast Nested Loop Join策略。这个策略的连接过程涉及到StreamTable和BuildTable的嵌套循环。

这种策略的性能可能会变得非常糟糕,对该策略进行的优化是在数据足够小时进行广播。如果连接为非等值连接如果规划的连接不是等值连接,则选择流程将转向非等值连接的一边,下图是选择流程已经进入了非等值连接判断的分支:

这里需要再次提醒一点:只有两种连接策略支持非等值连接,它们是Cartesian Product Join和Broadcast Nested Loop Join。在以上分支流程图中,如果在连接查询中指定了连接提示,请根据连接提示选择相应的连接策略。否则,如果数据集的一侧或两侧小到可以广播,则选择Broadcast Nested Loop Join策略并广播较小的数据集。如果没有足够小的数据集可以广播,则检查JointType是否为InnerLike,如果是,则选择Cartesian Product Join策略,否则就选择Broadcast Nested Loop Join策略作为最终方案。总结

本文介绍Spark SQL所提供的五种Join策略和三种影响Join性能的因素,并且详细讲解了五种Join策略的连接原理以及在不同的影响因素下如何选择合适的Join策略,希望对大家有所帮助。

发布于 2022-09-12 13:18