Spark SQL 的 Broadcast、SortMerge 和 ShuffleHash Join 怎么选?
本文目录
面试怎么回答
Broadcast Hash Join 适合小表,避免大表 Shuffle,但要控制分发、Executor 内存和并发;SortMerge Join 适合两张大表,较稳健,但有 Shuffle、排序和 Spill;Shuffle Hash Join 适合分区后一侧较小且能放入内存,可省排序但更怕估算错误。我会结合 Join 类型、过滤后统计、倾斜、AQE 和实际计划选择,而不是只背固定阈值。
先把这段讲顺;面试官追问时,再展开下面的原理和细节。
详细讲解
Join 策略的本质,是决定数据怎么移动、哪一侧构建结构、是否排序,以及这些成本能否被 Executor 内存和数据分布承受。
Broadcast Hash Join:把小表发到每个 Executor
当一侧足够小,可以广播到所有 Executor,并在本地构建 Hash Table。大表不需要按 Join Key Shuffle,通常能省掉大量网络和排序。
风险是广播数据会在 Driver 收集并分发,在 Executor 占用内存。表的文件大小、压缩后大小和运行时对象大小不是一回事;统计信息不准时,强制 Broadcast 可能超时或 OOM。
因此要看执行计划、表统计、广播阈值、并发和 Join 类型,而不是只背一个固定 MB 数字。
SortMerge Join:两边 Shuffle、排序后归并
两张大表做等值 Join 时,SortMerge Join 很常见。两边按 Key Shuffle 到相同分区,各自排序,再线性归并。
它能处理大数据量,不要求某一侧完整放入内存,但会产生网络、排序和 Spill 成本。Key 倾斜时,某个分区仍可能远大于其他分区。
Shuffle Hash Join:Shuffle 后在分区内建 Hash
Shuffle Hash Join 同样需要两边按 Key Shuffle,但不要求排序;它选择较小的一侧在每个分区内构建 Hash Table。
当一侧明显小于另一侧、分区后的 Build Side 能放进内存时,它可能避免排序成本。问题是内存更敏感,分区不均或估算错误时容易失败,所以不能只看全表大小。

实际选择要按这条路径
先确认 Join 类型和条件是否支持对应策略;再看两侧统计信息、过滤后大小和 Key 分布;然后评估广播或分区内 Hash 是否能安全放入内存;最后检查 AQE 是否会在运行时转换策略或处理倾斜分区。
Hint 只是给优化器的指示,不代表所有 Join 类型和物理条件下都会强制成功。上线前要用 EXPLAIN 和 Spark UI 看实际计划、Shuffle Read/Write、Spill、峰值内存和长尾 Task。

一个典型反例
某维表原始文件只有几百 MB,于是 SQL 强制 Broadcast。过滤和展开后对象体积远超预期,加上多个并发任务,每个 Executor 都承受一份 Hash Table,最终频繁 GC 和 OOM。
根因不是 Broadcast 一定不对,而是用存储大小代替了运行时内存,并忽略并发与数据膨胀。重新收集统计、提前裁剪列和过滤,再比较 Broadcast 与 SortMerge 的端到端成本,才是正确做法。