Join 按照关联形式(Join Types)划分 : 内关联、外关联、左关联、右关联
Spark SQL支持的关联形式 :
关联形式 | Join Type | 效果 |
---|---|---|
内关联 | inner | 结果集中只包含满足关联条件的数据 |
左外关联 | left/leftouter/left_outer | 内关联结果集+左表中不满足关联条件的剩余数据 |
右外关联 | right/rightouter/right_outer | 内关联结果集 + 右表中不满足关联条件的剩余数据 |
全外关联 | outer/full/fullouter/full_outer | 内关联结果集 + 左、右表中不满足关联条件的剩余数据 |
左半关联 | leftsemi/left_semi | 内关联结果集,但只保留左表部分的数据 |
左逆关联 | leftanti /left_anti | 左表中不满足关联条件的数据 |
内关联的效果 : 仅保留左右表中满足关联条件的那些数据记录
// 左表
salaries.show
/** 结果打印
+---+------+
| id|salary|
+---+------+
| 1| 26000|
| 2| 30000|
| 4| 25000|
| 3| 20000|
+---+------+
*/// 右表
employees.show
/** 结果打印
+---+-------+---+------+
| id| name|age|gender|
+---+-------+---+------+
| 1| Mike| 28| Male|
| 2| Lily| 30|Female|
| 3|Raymond| 26| Male|
| 5| Dave| 36| Male|
+---+-------+---+------+
*/// 内关联
val jointDF: DataFrame = salaries.join(employees, salaries("id") === employees("id"), "inner")jointDF.show
/** 结果打印
+---+------+---+-------+---+------+
| id|salary| id| name|age|gender|
+---+------+---+-------+---+------+
| 1| 26000| 1| Mike| 28| Male|
| 2| 30000| 2| Lily| 30|Female|
| 3| 20000| 3|Raymond| 26| Male|
+---+------+---+-------+---+------+
*/
外关联能细分 3 种形式:左外关联、右外关联、全外关联
左外关联,用 left/ leftouter/ left_outer
val jointDF: DataFrame = salaries.join(employees, salaries("id") === employees("id"), "left")jointDF.show
/** 结果打印
+---+------+----+-------+----+------+
| id|salary| id| name| age|gender|
+---+------+----+-------+----+------+
| 1| 26000| 1| Mike| 28| Male|
| 2| 30000| 2| Lily| 30|Female|
| 4| 25000|null| null|null| null|
| 3| 20000| 3|Raymond| 26| Male|
+---+------+----+-------+----+------+
*/
右外关联,用 right/ rightouter/ right_outer
val jointDF: DataFrame = salaries.join(employees, salaries("id") === employees("id"), "right")jointDF.show
/** 结果打印
+----+------+---+-------+---+------+
| id|salary| id| name|age|gender|
+----+------+---+-------+---+------+
| 1| 26000| 1| Mike| 28| Male|
| 2| 30000| 2| Lily| 30|Female|
| 3| 20000| 3|Raymond| 26| Male|
|null| null| 5| Dave| 36| Male|
+----+------+---+-------+---+------+
*/
全外关联,用 full/ outer/ ullouter/ full_outer
val jointDF: DataFrame = salaries.join(employees, salaries("id") === employees("id"), "full")jointDF.show
/** 结果打印
+----+------+----+-------+----+------+
| id|salary| id| name| age|gender|
+----+------+----+-------+----+------+
| 1| 26000| 1| Mike| 28| Male|
| 3| 20000| 3|Raymond| 26| Male|
|null| null| 5| Dave| 36| Male|
| 4| 25000|null| null|null| null|
| 2| 30000| 2| Lily| 30|Female|
+----+------+----+-------+----+------+
*/
左半关联,用 leftsemi/left_semi
// 左半关联
val jointDF: DataFrame = salaries.join(employees, salaries("id") === employees("id"), "left_semi")jointDF.show
/** 结果打印
+---+------+
| id|salary|
+---+------+
| 1| 26000|
| 2| 30000|
| 3| 20000|
+---+------+
*/
左逆关联,用 leftanti/left_anti
// 左逆关联
val jointDF: DataFrame = salaries.join(employees, salaries("id") === employees("id"), "left_anti")jointDF.show
/** 结果打印
+---+------+
| id|salary|
+---+------+
| 4| 25000|
+---+------+
*/
Join 有 3 种实现机制 :
俗定 : 左表 = 驱动表,右表 = 基表
Join实现机制 | 范围 | 效率 | 工作原理 |
---|---|---|---|
Nested Loop Join | 全部关联 | 最差 | 用嵌套循环来实现关联,效率最低,算法复杂度为 O(M * N) |
Sort Merge Join | 等值关联 | 次优 | 先将两表排序,再用游标滑动实现关联,算法复杂度为 O(M + N) |
Hash Join | 等值关联 | 最优 | 关联过程分两阶段:Build:用哈希算法对基表建立哈希表。Probe:遍历驱动表每条数据,动态计算哈希值,再找哈希表来实现关联计算。复杂度为 O(M) |
NLJ (Nested Loop Join ) 的实现机制:用外、内两个嵌套的 for 循环,来依次扫描驱动表与基表中的数据记录
O(M * N)
SMJ (Sort Merge Join) 的实现思路 : 先排序、再归并
O(M + N)
游标对比的 3 种情况:
HJ (Hash Join) 的设计初衷 : 以空间换时间,将基表的计算复杂度降到 O(1)
HJ 的计算的两个阶段:Build 阶段和 Probe 阶段
Join 按照分发模式划分 : Shuffle Join、Broadcast Join
Join策略 | 前提条件 | 优势 | 劣势 |
---|---|---|---|
Shuffle Join | 无 | 适用范围广,不受数据体量、内存大小 | 会有 l/O开销,容易性能瓶颈 |
Broadcast Join | 基表 < Executors 内存 | 只需广播基表,消除驱动表的 Shuffle 过程,执行效率高 | 无 |
用 Shuffle 完成数据关联 :
用广播机制完成数据关联 :
6 种分布式 Join :
Spark SQL 的5 种 Join :
关联条件 | Join 策略排序 |
---|---|
等值关联 | Broadcast HJ > Shuffle SMJ > Shuffle HJ |
不等值关联 | Broadcast NLJ > Shuffle NLJ |
等值数据关联时,Spark 会按照 BHJ > SMJ > SHJ 的顺序选择 Join 策略
BHJ 效率最高,前提条件:
SHJ 前提条件:
spark.sql.join.preferSortMergeJoin
为 False 时,Spark SQL 才会先尝试 SHJ
不等值 Join 只能用 BNLJ和 CPJ