SparkSQL Join深度解析:三种实现方式全揭秘

你好,我是 shengjk1,多年大厂经验,努力构建 通俗易懂的、好玩的编程语言教程。 欢迎关注!你会有如下收益:

  1. 了解大厂经验
  2. 拥有和大厂相匹配的技术等

希望看什么,评论或者私信告诉我!

一、背景

SparkSQL 现在基本上可以说是离线计算的大拿了,所以掌握了 SparkSQL 的 Join 也就相当于掌握了这位大拿。

一直想要总结一下,今天遇到了 Broadcast 的一些事情,终于可以顺便把 SparkSQL 的 Join 总结一下

二、引言

Join操作是SQL语言中常用的操作,主要用于建立多表之间的连接关系。在SparkSQL中,Join操作有多种实现方式,每种实现方式都有其特定的原理和应用场景。本报告将详细介绍SparkSQL中Join的实现方式,包括Broadcast Join、Hash Join(包括Shuffle Hash Join)和Sort Merge Join,分析它们的工作原理、实现机制以及适用场景,帮助读者深入理解SparkSQL中Join操作的内部实现。

三、SparkSQL Join概述

Join是SQL语句中的一种常用操作,主要用于连接两个或多个表中的数据。良好的表结构能够将数据分散在不同的表中,使其符合某种范式,减少数据冗余、更新容错等。而建立表和表之间关系的最佳方式就是Join操作。 在SparkSQL中,Join操作主要分为以下几种实现方式:

  1. Broadcast Join :适用于小表对大表的连接
  2. Hash Join :包括Shuffle Hash Join,适用于中等大小表与大表的连接
  3. Sort Merge Join :适用于两张大表之间的连接

3.1Broadcast Join

3.1.1 实现原理

Broadcast Join是一种将小表广播到所有工作节点的Join实现方式。在数仓的常见模型中(比如星型模型或者雪花模型),表一般分为两种:事实表和维度表。事实表通常包含大量的业务数据(大表),而维度表则包含描述信息(小表)。 Broadcast Join的实现原理如下

0. 确定小表 在执行 Broadcast Join 时,首先需要确定哪个表是小表(通常称为广播表)。这个表通常会根据大小或配置被选择为广播表。

1. 将小表(维度表)收集到Driver端 确定小表后,Spark 会将小表的所有数据收集到 Driver 节点。这个过程是通过 collect 操作完成的,它会将小表的所有数据拉取到 Driver 内存中。这一步是必要的,因为广播机制需要将小表的完整数据分发到所有 Executor 节点。

2. 将小表广播到所有Executor节点 Driver 节点将小表的数据封装成一个广播变量(Broadcast Variable),然后通过 Spark 的广播机制将这个广播变量发送到所有 Executor 节点。广播机制会优化数据的传输,避免重复发送相同的数据。

3. 每个Executor节点在本地内存中建立小表的Hash表 每个 Executor 节点接收到广播的小表数据后,会在本地内存中构建一个 Hash 表。这个 Hash 表是基于小表的连接键构建的,用于快速查找匹配的记录。

4. 大表的数据被分发到各个Executor节点 大表的数据会按照正常的分区策略被分发到各个 Executor 节点。每个 Executor 节点会处理分配给它的大表数据分区。

5. 每个Executor节点遍历大表的数据,使用Hash表进行连接操作 每个 Executor 节点遍历大表的数据分区,使用之前构建的 Hash 表进行连接操作。通过 Hash 表的快速查找,可以高效地找到匹配的记录并生成连接结果。

这种实现方式的核心思想是通过广播小表,避免了大表和小表之间的数据重分布,从而减少网络传输开销。

3.1.2 适用场景

Broadcast Join适用于以下场景:

  • 一张小表和一张大表进行Join
  • 小表足够小,可以直接广播到所有Executor节点
  • 小表可以完全缓存到内存中

3.1.3优缺点

优点

  • 减少了网络传输开销,因为小表被广播到所有节点
  • 避免了Shuffle操作,提高了Join效率
  • 适用于小表对大表的连接场景 缺点
  • 如果小表较大,广播操作会消耗大量网络带宽
  • 每个Executor节点需要存储完整的小表副本,增加了内存消耗
  • 小表过大时,可能会导致内存不足或性能下降

3.1.4 实现细节

在Spark中,Broadcast Join的实现细节包括:

  1. 广播机制:Spark通过广播机制将小表分发到所有Executor节点。广播的数据可以存储在每个Executor的内存中,以便快速访问。
  2. Hash表构建:每个Executor节点在接收到广播的小表后,会构建一个Hash表,以便快速查找Join键。
  3. 连接操作:对于大表中的每条记录,Executor节点会使用Hash表进行快速查找,完成Join操作。
  4. 内存管理:Spark会尝试将广播的数据缓存到内存中,但如果内存不足,可能会将数据溢写到磁盘。

3.2 Hash Join

Hash Join是另一种常见的Join实现方式,包括Shuffle Hash Join。Hash Join的基本原理是使用Hash表来存储一个表的数据,然后通过Hash函数快速查找匹配的记录。,适用于中等大小表与大表的连接

3.2.1 Hash Join的基本原理

Hash Join的基本实现原理如下:

  1. 选择构建侧
  • 在连接操作中,通常会选择较小的表作为构建侧。这是因为较小的表更适合构建在内存中的 Hash 表,从而减少磁盘 I/O 操作。
  • 如果两个表的大小差异不大,可能需要根据其他因素(如数据分布、内存限制等)来决定哪一侧作为构建侧。
  1. 构建Hash表
  • 对构建侧表的记录进行扫描,根据连接键(Join Key)计算 Hash 值。
  • 将记录存储到一个 Hash 表中,Hash 表的结构通常是基于 Hash 值的数组或链表。
  • 这一步的目的是将构建侧的记录组织起来,以便后续快速查找
  1. Probe阶段
  • 对另一侧(Probe Side)的记录进行扫描。
  • 对每条记录的连接键计算相同的 Hash 值。
  • 使用这个 Hash 值在构建侧的 Hash 表中查找匹配的记录。
  1. 查找和连接
  • 如果在 Hash 表中找到匹配的记录,则将 Probe 侧的记录与匹配的记录进行连接,生成结果。
  • 如果没有找到匹配的记录,则忽略该记录。 Hash Join的核心思想是通过Hash表实现快速查找,减少连接操作的时间复杂度。

3.2.2 Shuffle Hash Join

Shuffle Hash Join是Hash Join的一种变体。

Shuffle Hash Join 的特点: 1. Shuffle 操作:

  • 在分布式环境中,数据通常分布在多个节点上。

  • 为了进行 Hash Join,可能需要将数据重新分区(Shuffle),使得连接键相同的记录被分配到同一个节点上。

  • Shuffle 操作是分布式计算中的一个开销较大的步骤,因为它涉及跨节点的数据传输。 2. AQEShuffleRead:

  • AQEShuffleRead 是 Apache Spark 的一个优化特性,AQE(Adaptive Query Execution)表示自适应查询执行。

  • 在 AQE 模式下,Spark 会根据运行时的信息(如数据大小、数据分布等)动态调整 Shuffle 操作。 AQEShuffleRead 的目的是优化 Shuffle 过程,减少不必要的数据传输和计算开销。

3.2.3实现原理

Shuffle Hash Join的实现原理如下:

  1. Shuffle阶段: 对两个表分别按照Join键进行重分区(Shuffle)。由于使用相同的分区函数,相同Join键的记录会被分配到相同的分区中。
  2. 分区处理: 将每个分区的数据分发到相应的Executor节点进行处理。
  3. 本地Hash Join : 在每个Executor节点上,对本地的两个分区数据进行Hash Join操作。具体步骤包括:
    • 构建Hash表:使用构建侧的数据构建Hash表
    • Probe阶段:使用Probe侧的数据进行查找和连接

3.2.4 适用场景

Shuffle Hash Join适用于以下场景:

  • 一张小表(比适合Broadcast Join的表稍大)和一张大表进行Join
  • 两张中等大小的表进行Join
  • 数据分布不均匀,需要通过Shuffle进行均衡

3.2.5优缺点

优点

  • 通过Shuffle确保相同Join键的数据在同一个分区中,减少了Hash冲突
  • 比Broadcast Join更适合处理稍大的小表
  • 并行度较高,处理速度快 缺点
  • 需要进行Shuffle操作,增加了网络传输开销
  • 如果Join键选择不当,可能导致数据分布不均匀,出现数据倾斜
  • 内存使用较多,如果数据量过大可能会导致内存不足

3.2.6 Shuffle Hash Join的实现细节

Shuffle Hash Join的实现细节包括:

  1. 分区策略: 使用相同的分区函数对两个表进行分区,确保相同Join键的数据在同一个分区中。

  2. Shuffle操作: 将数据按照分区键进行重分布,将数据从各个节点发送到对应的分区所在的节点,这个过程称为Shuffle。 Shuffle 是分布式计算中一个关键且资源密集型的步骤,其效率直接影响整个 Join 操作的性能。

  3. Hash表构建: 在每个Executor节点上,使用构建侧的数据构建Hash表。

  4. Probe阶段: 使用Probe侧的数据进行查找和连接,并生成最终的 Join 结果。

  5. 内存管理: 当构建侧的数据量较大,超出单个节点的可用内存时,为了避免内存溢出导致程序崩溃,会启动溢写(Spill)操作。将部分数据写入磁盘,从而释放内存空间,确保 Join 操作能够继续进行。同时,合理配置内存参数和优化数据结构,尽量减少溢写的发生,以提高整体性能。

3.3 Sort Merge Join

Sort Merge Join是另一种Join实现方式,适用于两张大表 之间的连接。

3.3.1 实现原理

Sort Merge Join的实现原理如下: 0. Shuffle 将两个数据集按照连接键进行分区,确保相同键的数据被分配到同一个节点上[

  1. 排序阶段
  • 对两个参与 Join 操作的表分别按照 Join 键进行全局排序
  • 排序完成后,将两个表的数据划分为有序的数据块,便于后续的合并操作
  1. 合并阶段 :类似于归并排序,同时遍历两个排序后的数据集,比较当前记录的Join键:
    • 如果Join键相等,进行连接操作
    • 如果Join键不等,移动指针到较小的那一侧
  2. 处理重复键: 如果存在重复的Join键,需要处理多个匹配的情况,需要对每个重复键的记录集合进行笛卡尔积操作

Sort Merge Join的工作原理类似于数据库中的归并连接,通过排序和合并操作完成Join操作。

3.3.2 适用场景

Sort Merge Join适用于以下场景:

  • 两张大表进行Join
  • 内存不足以存储一张完整的表

3.3.3 优缺点

优点

  • 不需要将一侧数据全部加载到内存中,而是即用即丢,提升了大数量下sql join的稳定性
  • 适用于两张大表之间的连接
  • 内存使用相对较少 缺点
  • 需要对数据进行排序,增加了计算开销
  • 如果Join键分布不均匀,可能导致性能下降
  • 不支持非等值连接

##四、 Join策略的选择 在SparkSQL中,选择合适的Join策略对于性能至关重要。以下是选择Join策略的建议:

基于表大小的选择

  1. 小表对大表
    • 如果小表非常小,适合使用Broadcast Join
    • 如果小表稍大,适合使用Shuffle Hash Join
  2. 中等大小表对大表
    • 适合使用Shuffle Hash Join
  3. 大表对大表
    • 适合使用Sort Merge Join

基于数据分布的选择

  1. 数据分布均匀
    • Shuffle Hash Join表现较好
  2. 数据分布不均匀(存在数据倾斜)
    • Sort Merge Join可能更稳定

基于内存资源的选择

  1. 内存资源充足
    • 可以考虑使用Broadcast Join或Shuffle Hash Join
  2. 内存资源有限
    • 考虑使用Sort Merge Join

SparkSQL Join的性能优化

为了提高SparkSQL Join操作的性能,可以采取以下优化措施:

1. 选择合适的Join策略

根据表的大小、数据分布和内存资源选择合适的Join策略。Spark会根据统计信息自动选择Join策略,但有时需要显式指定。

2. 提示(Hints)

Spark提供了Join提示,允许用户指定Join策略。常用的Join提示包括:

  • BROADCAST:广播Join,适用于小表对大表
  • MERGE:Sort Merge Join,适用于大表对大表
  • SHUFFLE_HASH:Shuffle Hash Join,适用于中等大小表对大表
  • SHUFFLE_REPLICATE_NL:Shuffle Nested Loop Join,适用于小表对大表且内存不足的情况
sql 复制代码
SELECT /*+ BROADCAST(a) */ a.key, a.value, b.description
FROM small_table a JOIN large_table b ON a.key = b.key

3. 调整配置参数

Spark提供了多个配置参数可以影响Join性能:

  • spark.sql.shuffle.partitions:控制Shuffle操作的分区数,影响并行度和内存使用
  • spark.sql.autoBroadcastJoinThreshold:控制自动广播Join的阈值,超过这个值的表不会被自动广播
  • spark.sql.join.allowImplicit cartesianProduct:是否允许笛卡尔积连接

4. 数据分区优化

合理的数据分区可以减少Join操作的数据移动:

  • 使用相同的分区列和分区方式
  • 避免不必要的数据重分区
  • 考虑按照Join键进行分区

5. 内存管理

合理管理内存使用:

  • 使用合适的内存配置
  • 调整内存使用比例(如spark.memory.fraction)
  • 考虑使用持久化(Persistence)优化内存使用

6. 数据倾斜处理

处理数据倾斜问题:

  • 使用Bucketed Join(桶连接) -- 表必须已经分桶 :两个表需要按照相同的键进行分桶,并且桶的数量必须相同。 -- 数据必须有序 :桶内的数据需要按照 Join 键排序,这样可以进一步优化 Join 操作。 -- Join 键必须一致:两个表的 Join 键必须相同,并且是等值连接 在执行 Join 操作时,Spark SQL 会自动检测表是否已经分桶,并且是否满足 Bucket Join 的条件。如果满足条件,Spark SQL 会自动选择 Bucket Join 策略
  • 使用Salting技术 为倾斜的Key添加随机前缀,分散数据。
  • 调整Join顺序 先对较小的数据集进行Join,减少倾斜影响

五、总结

SparkSQL的Join操作是离线计算中的关键环节,其性能直接影响数据分析的效率。Broadcast Join适用于小表对大表的连接,通过广播小表减少网络传输开销;Hash Join(包括Shuffle Hash Join)适用于中等大小表与大表的连接,通过Hash表实现快速查找;Sort Merge Join则适用于两张大表的连接,通过排序和合并操作完成Join。选择合适的Join策略并结合性能优化措施,可以显著提升SparkSQL Join操作的效率和稳定性

相关推荐
来自星星的坤14 分钟前
SpringBoot 与 Vue3 实现前后端互联全解析
后端·ajax·前端框架·vue·springboot
AUGENSTERN_dc20 分钟前
RaabitMQ 快速入门
java·后端·rabbitmq
烛阴1 小时前
零基础必看!Express 项目 .env 配置,开发、测试、生产环境轻松搞定!
javascript·后端·express
燃星cro1 小时前
参照Spring Boot后端框架实现序列化工具类
java·spring boot·后端
追逐时光者4 小时前
C#/.NET/.NET Core拾遗补漏合集(25年4月更新)
后端·.net
FG.4 小时前
GO语言入门
开发语言·后端·golang
转转技术团队5 小时前
加Log就卡?不加Log就瞎?”——这个插件治好了我的精神
java·后端
谦行5 小时前
前端视角 Java Web 入门手册 5.5:真实世界 Web 开发——控制反转与 @Autowired
java·后端
uhakadotcom5 小时前
PyTorch 2.0:最全入门指南,轻松理解新特性和实用案例
后端·面试·github
bnnnnnnnn5 小时前
前端实现多服务器文件 自动同步宝塔定时任务 + 同步工具 + 企业微信告警(实战详解)
前端·javascript·后端