转:Spark SQL中Join常用3种实现

本文介绍SparkSQL中的三种Join实现:BroadcastJoin、ShuffleHashJoin和SortMergeJoin,旨在减少shuffle操作,提升大数据处理效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

注:本文出处http://blog.csdn.NET/asongoficeandfire/article/details/53574034



引言

Join是SQL语句中的常用操作,良好的表结构能够将数据分散在不同的表中,使其符合某种范式,减少表冗余、更新容错等。而建立表和表之间关系的最佳方式就是Join操作。

SparkSQL作为大数据领域的SQL实现,自然也对Join操作做了不少优化,今天主要看一下在SparkSQL中对于Join,常见的3种实现。


SparkSQL的3种Join实现

Broadcast Join

大家知道,在数据库的常见模型中(比如星型模型或者雪花模型),表一般分为两种:事实表和维度表。维度表一般指固定的、变动较少的表,例如联系人、物品种类等,一般数据有限。而事实表一般记录流水,比如销售清单等,通常随着时间的增长不断膨胀。

因为Join操作是对两个表中key值相同的记录进行连接,在SparkSQL中,对两个表做Join最直接的方式是先根据key分区,再在每个分区中把key值相同的记录拿出来做连接操作。但这样就不可避免地涉及到shuffle,而shuffle在Spark中是比较耗时的操作,我们应该尽可能的设计Spark应用使其避免大量的shuffle。

当维度表和事实表进行Join操作时,为了避免shuffle,我们可以将大小有限的维度表的全部数据分发到每个节点上,供事实表使用。executor存储维度表的全部数据,一定程度上牺牲了空间,换取shuffle操作大量的耗时,这在SparkSQL中称作Broadcast Join,如下图所示:


Table B是较小的表,黑色表示将其广播到每个executor节点上,Table A的每个partition会通过block manager取到Table A的数据。根据每条记录的Join Key取到Table B中相对应的记录,根据Join Type进行操作。这个过程比较简单,不做赘述。

Broadcast Join的条件有以下几个:

1. 被广播的表需要小于spark.sql.autoBroadcastJoinThreshold所配置的值,默认是10M (或者加了broadcast join的hint)

2. 基表不能被广播,比如left outer join时,只能广播右表

看起来广播是一个比较理想的方案,但它有没有缺点呢?也很明显。这个方案只能用于广播较小的表,否则数据的冗余传输就远大于shuffle的开销;另外,广播时需要将被广播的表现collect到driver端,当频繁有广播出现时,对driver的内存也是一个考验。


Shuffle Hash Join

当一侧的表比较小时,我们选择将其广播出去以避免shuffle,提高性能。但因为被广播的表首先被collect到driver段,然后被冗余分发到每个executor上,所以当表比较大时,采用broadcast join会对driver端和executor端造成较大的压力。

但由于Spark是一个分布式的计算引擎,可以通过分区的形式将大批量的数据划分成n份较小的数据集进行并行计算。这种思想应用到Join上便是Shuffle Hash Join了。利用key相同必然分区相同的这个原理,SparkSQL将较大表的join分而治之,先将表划分成n个分区,再对两个表中相对应分区的数据分别进行Hash Join,这样即在一定程度上减少了driver广播一侧表的压力,也减少了executor端取整张被广播表的内存消耗。其原理如下图:


Shuffle Hash Join分为两步:

1. 对两张表分别按照join keys进行重分区,即shuffle,目的是为了让有相同join keys值的记录分到对应的分区中

2. 对对应分区中的数据进行join,此处先将小表分区构造为一张hash表,然后根据大表分区中记录的join keys值拿出来进行匹配

Shuffle Hash Join的条件有以下几个:

1. 分区的平均大小不超过spark.sql.autoBroadcastJoinThreshold所配置的值,默认是10M 

2. 基表不能被广播,比如left outer join时,只能广播右表

3. 一侧的表要明显小于另外一侧,小的一侧将被广播(明显小于的定义为3倍小,此处为经验值)

我们可以看到,在一定大小的表中,SparkSQL从时空结合的角度来看,将两个表进行重新分区,并且对小表中的分区进行hash化,从而完成join。在保持一定复杂度的基础上,尽量减少driver和executor的内存压力,提升了计算时的稳定性。


Sort Merge Join

上面介绍的两种实现对于一定大小的表比较适用,但当两个表都非常大时,显然无论适用哪种都会对计算内存造成很大压力。这是因为join时两者采取的都是hash join,是将一侧的数据完全加载到内存中,使用hash code取join keys值相等的记录进行连接。

当两个表都非常大时,SparkSQL采用了一种全新的方案来对表进行Join,即Sort Merge Join。这种实现方式不用将一侧数据全部加载后再进星hash join,但需要在join前将数据排序,如下图所示:


可以看到,首先将两张表按照join keys进行了重新shuffle,保证join keys值相同的记录会被分在相应的分区。分区后对每个分区内的数据进行排序,排序后再对相应的分区内的记录进行连接,如下图示:


看着很眼熟吧?也很简单,因为两个序列都是有序的,从头遍历,碰到key相同的就输出;如果不同,左边小就继续取左边,反之取右边。

可以看出,无论分区有多大,Sort Merge Join都不用把某一侧的数据全部加载到内存中,而是即用即取即丢,从而大大提升了大数据量下sql join的稳定性。


后记

本文介绍了SparkSQL中的3中Join实现,其实这也不是什么新鲜玩意儿。传统DB也有这也的玩法儿,SparkSQL只是将其做成分布式的实现。

本文仅仅从大的理论方面介绍了这几种实现,具体到每个join type是怎么遍历、没有join keys时应该怎么做、这些实现对join keys有什么具体的需求,这些细节都没有展现出来。感兴趣的话,可以去翻翻源码。


<think>我们正在讨论SparkSQL中的join操作类型。根据用户的要求,他们想了解SparkSQLjoin操作的常见类型。我们需要结合之前的引用内容,但注意之前的引用主要涉及join操作中的字段类型一致性问题以及using语法,并不是直接介绍join类型。因此,我们需要主要介绍SparkSQL支持的常见join类型,同时可以适当结合引用中的内容进行补充说明。SparkSQL中常见的join类型包括:1.内连接(INNERJOIN):只返回两个表中匹配的行。2.外连接(OUTERJOIN):-左外连接(LEFTOUTERJOIN):返回左表的所有行,以及右表中匹配的行。如果右表没有匹配,则结果中右表的部分为NULL。-右外连接(RIGHTOUTERJOIN):返回右表的所有行,以及左表中匹配的行。如果左表没有匹配,则结果中左表的部分为NULL。-全外连接(FULLOUTERJOIN):返回左右两个表的所有行。当某一行在另一个表中没有匹配时,另一个表的部分为NULL。3.交叉连接(CROSSJOIN):返回两个表的笛卡尔积。4.半连接(SEMIJOIN):返回左表中那些在右表中有匹配的行。注意,结果中只包含左表的列。5.反连接(ANTIJOIN):返回左表中那些在右表中没有匹配的行。此外,SparkSQL还支持使用using语法进行连接,这种语法可以简化连接条件,当两个表具有相同名称的列时,可以使用using(列名)来指定连接键,并且结果中不会重复出现该列(引用[2]中提到了using语法)。另外,在SparkSQL中,如果连接键的类型不一致,Spark可能会在连接前进行隐式换(如引用[1]中的例子,将bigint类型和string类型通过cast换为double类型来匹配),这可能会影响性能,因此建议在连接前保持字段类型一致。接下来,我们将介绍每种join类型的语法和示例,并注意使用引用标识。注意:由于用户要求使用中文回答,并且按照格式要求进行数学公式的书写(但本问题可能不涉及数学公式,所以主要注意代码块和引用标识)。我们首先介绍各种连接类型,并给出示例代码和解释。</think> ### Spark SQL Join 操作常见类型详解 在 Spark SQL 中,Join 操作是最核心的数据处理技术之一。以下是主要的 Join 类型及其特点[^1][^2]: #### 1. **内连接 (INNER JOIN)** - **功能**: 仅返回两个表中**匹配成功**的记录 - **执行计划标识**: `InnerJoin` 或 `BroadcastHashJoin` - **语法示例**: ```sql SELECT * FROM t1 INNER JOIN t2 ON t1.key = t2.key ``` - **结果特征**: ``` | key | value1 | value2 | |-----|--------|--------| | K1 | V1 | W1 | # 匹配成功 | K2 | V2 | W2 | # 匹配成功 ``` *注:不匹配的记录会被过滤掉* #### 2. **外连接 (OUTER JOIN)** - **左外连接 (LEFT OUTER JOIN)** - 返回左表全部记录 + 右表匹配记录 - 右表无匹配时填充 `NULL` - 执行计划标识: `LeftOuterJoin` - **右外连接 (RIGHT OUTER JOIN)** - 返回右表全部记录 + 左表匹配记录 - 左表无匹配时填充 `NULL` - 执行计划标识: `RightOuterJoin` - **全外连接 (FULL OUTER JOIN)** - 返回两表所有记录 - 无匹配时双方填充 `NULL` - 执行计划标识: `FullOuterJoin` #### 3. **交叉连接 (CROSS JOIN)** - 返回两表的笛卡尔积 - 数据量: `左表行数 × 右表行数` - **语法示例**: ```sql SELECT * FROM t1 CROSS JOIN t2 ``` #### 4. **半连接 (SEMI JOIN)** - 类似 `IN` 子查询效果 - 返回左表中**存在于**右表的记录 - **显著特征**: 结果集仅包含左表字段 - **执行计划标识**: `LeftSemiJoin` #### 5. **反连接 (ANTI JOIN)** - 返回左表中**不存在于**右表的记录 - **典型用途**: 数据校验和异常检测 - **执行计划标识**: `LeftAntiJoin` #### 6. **广播连接 (BroadcastHashJoin)** - **优化策略**: 将小表广播到所有Executor - **触发条件**: 小表尺寸 < `spark.sql.autoBroadcastJoinThreshold`(默认10MB) - **执行计划标识**: ``` == Physical Plan == BroadcastHashOuterJoin [...] # 引用[1]示例 ``` #### 7. **Using 语法简化** - **功能**:自动消除重复字段 - **等价的 Spark API**: ```python df1.join(df2, Seq("join_column"), "inner") # 引用[2]说明 ``` #### 🔍 性能注意事项 1. **类型一致性** Join 字段类型不一致会触发隐式换(如 `cast(age#98L as double)`),显著降低性能[^1] 2. **广播优化** 对小表优先使用广播连接可避免数据Shuffle 3. **执行计划分析** 通过 `.explain()` 检查物理计划中的 Join 类型: ```python df.join(...).explain() # 输出示例: BroadcastHashJoin / SortMergeJoin... ``` --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值