Join
针对Join端产生的数据倾斜,会存在多种不同的情况,例如大表和小表Join、大表和中表Join、Join热点值长尾。
大表Join小表
- 数据倾斜示例
如下示例中t1是一张大表,t2、t3是小表。
SELECT t1.ip ,t1.is_anon ,t1.user_id ,t1.user_agent ,t1.referer ,t2.ssl_ciphers ,t3.shop_province_name ,t3.shop_city_name FROM viewtable t1 LEFT OUTER JOIN other_viewtable t2 ON t1.header_eagleeye_traceid = t2.eagleeye_traceid LEFT OUTER JOIN ( SELECT shop_id ,city_name AS shop_city_name ,province_name AS shop_province_name FROM tenanttable WHERE ds = (SELECT max(ds) FROM tenanttable) AND is_valid = 1 ) t3 ON t1.shopid = t3.shop_id - 解决方案
使用Hint(查询优化提示指令)语法,广播小表,如下所示。
SELECT /*+ BROADCAST(t2, t3) */ t1.ip ,t1.is_anon ,t1.user_id ,t1.user_agent ,t1.referer ,t2.ssl_ciphers ,t3.shop_province_name ,t3.shop_city_name FROM viewtable t1 LEFT OUTER JOIN other_viewtable t2 ON t1.header_eagleeye_traceid = t2.eagleeye_traceid LEFT OUTER JOIN ( SELECT shop_id ,city_name AS shop_city_name ,province_name AS shop_province_name FROM tenanttable WHERE ds = (SELECT max(ds) FROM tenanttable) AND is_valid = 1 ) t3 ON t1.shopid = t3.shop_id - 注意事项
- Spark支持自动触发广播,阈值配置参数为spark.sql.autoBroadcastJoinThreshold=10485760(默认值为10MB)。手动Hint则不受阈值参数限制,需要注意广播引起的Driver/Executor内存膨胀问题。
- 引用小表或子查询时,需要引用别名。
Join热点值长尾
- 数据倾斜示例
在下面这个表中,eleme_uid中存在很多热点数据,容易发生数据倾斜。
SELECT eleme_uid, ... FROM ( SELECT eleme_uid, ... FROM <viewtable> ) t1 LEFT JOIN ( SELECT eleme_uid, ... FROM <customertable> ) t2 ON t1.eleme_uid = t2.eleme_uid; - 解决方案
可以通过如下三种方法解决。
序号
方案
说明
方案一
手动切分热点值
将热点值分析出来后,从主表中过滤出热点值记录先进行广播(Broadcast Join),剩余非热点记录走Sort Merge Join,最后UNION ALL。
方案二
AQE Skew Join / SkewJoin Hint
AQE自动检测倾斜key拆分;也可手动用 /*+ SKEW */ Hint。
方案三
倍数表取模相等Join
利用倍数表将热点值放大N倍,然后Join时使用用户ID和number两个关联键。
- 手动切分热点值。
将热点值分析出来后,从主表中过滤出热点值记录,先进行广播,再将剩余非热点值记录进行Sort Merge Join,最后合并两部分的Join结果。具体可以参考如下代码示例:
-- 热点值走Broadcast Join SELECT /*+ BROADCAST(t2) */ eleme_uid, ... FROM (SELECT eleme_uid, ... FROM viewtable WHERE eleme_uid = <skewed_value>) t1 LEFT JOIN (SELECT eleme_uid, ... FROM customertable WHERE eleme_uid = <skewed_value>) t2 ON t1.eleme_uid = t2.eleme_uid UNION ALL -- 非热点值走Sort Merge Join SELECT eleme_uid, ... FROM (SELECT eleme_uid, ... FROM viewtable WHERE eleme_uid != <skewed_value>) t3 LEFT JOIN (SELECT eleme_uid, ... FROM customertable WHERE eleme_uid != <skewed_value>) t4 ON t3.eleme_uid = t4.eleme_uid - AQE Skew Join / SkewJoin Hint。
spark.sql.adaptive.enabled=true spark.sql.adaptive.skewJoin.enabled=true
Spark也支持手动Hint(查询优化提示指令)指定倾斜值,使用示例如下:
SELECT /*+ SKEW('a', 'c0', (1, '2'), (3, '4')) */ * FROM T0 a JOIN T1 b ON a.c0 = b.c0 AND a.c1 = b.c1 AND a.c2 = b.c2;支持的Join类型:
- Inner Join可以对Join两侧表中的任意一侧进行Hint。
- Left Join、Semi Join和Anti Join只可以Hint左侧表。
- Right Join只可以Hint右侧表。
- Full Join不支持Skew Join Hint。
建议只对一定会出现数据倾斜的Join添加Hint,因为Hint会运行一个Aggregate,存在一定代价。Join key类型必须一致(两侧字段类型相同),否则Hint不生效;可通过CAST在子查询中统一类型。
- 倍数表取模相等Join
该方案和前两个方案的逻辑不同,不是分而治之的思路,而是利用一个倍数表,其值只有一列:int列,比如可以是从1到N(具体可根据倾斜程度确定),利用这个倍数表可以将用户行为表放大N倍,然后Join时使用用户ID和number两个关联键。这样原先只按照用户ID分发导致的数据倾斜就会由于加入了number关联条件而减少为原先的1/N。但是这样做也会导致数据膨胀N倍。
SELECT eleme_uid, ... FROM (SELECT eleme_uid, ... FROM viewtable) t1 LEFT JOIN (SELECT /*+ BROADCAST(multipletable) */ eleme_uid, number, ... FROM customertable JOIN multipletable) t2 ON t1.eleme_uid = t2.eleme_uid AND mod(t1.value_col, 10) + 1 = t2.number
- 手动切分热点值。