内容纲要
概要描述
本文主要介绍 NULL值引发的数据倾斜,如何处理。
详细说明
我们先举个简单的例子,说下 NULL 值数据倾斜的具体现象:
SELECT
a.serialno,a.recipeno,
if(b.serialno IS NOT NULL AND substr(orderid,1,1)='C',1,0) AS flag
FROM ods.ods_hisdb_recipedetail a
LEFT JOIN ods.ods_hisdb_surgeryinfo b
ON a.docnumber = concat('S',b.serialno) and b.tabletype='mid';
语句执行一直卡在common join 的stage的最后1个task上面(一般来说task的index num 为0)
检查左表joinkey分布情况,null值2.7亿+:
SELECT docnumber AS jk1,count(*) AS cnt1
FROM ods.ods_hisdb_recipedetail a
WHERE a.tabletype='mid'
GROUP BY docnumber
ORDER BY 2 DESC LIMIT 10;
检查右表joinkey分布情况,都是唯一的:
SELECT concat('S',b.serialno) AS jk2,count(*) AS cnt2
FROM ods.ods_hisdb_surgeryinfo b
GROUP BY concat('S',b.serialno)
ORDER BY 2 DESC LIMIT 10;
方案一:改写sql,业务逻辑上剔除
和客户沟通下,joinkey为 null 的数据能否过滤掉,过滤之后是否影响业务。
改写 sql 方案作为首选方案,如果客户接受就不需要下面的备选方案了。
方案二: skewjoin:
Argodb6.0版本开始支持skewjoin的解决方案,
--null倾斜设置开关,默认false
SET quark.join.null.optimize=TRUE;
--非null倾斜设置开关,默认false,启动 SKEWJOIN hint 解析
--SET quark.skewjoin.hint.enable=TRUE;
--打开可支持读ORC表,默认false (目前linac还无法支持orc表,windrunner是完整支持orc的)
SET ngmr.windrunner.session.orc=TRUE;
--对非query类型的sql开启windrunner,开启后delete/update/insert才会走新引擎
--SET ngmr.windrunner.nonquery.enabled=TRUE;
--再执行sql即可(sql内容不需要做修改)
方案三:强制mapjoin hint (慎用)
该方案一般适用于,客户着急跑数出结果,事先需要先看下小表的hdfs大小,过大则不适合走mapjoin。
注意:join两侧的表大小都较大默认无法mapjoin时,不建议使用该方案。 另外,如果后期表的数据量变动,极易造成executor异常,所以请慎用。
SELECT
/*+ mapjoin(b)*/ a.serialno,a.recipeno,
if(b.serialno IS NOT NULL AND substr(orderid,1,1)='C',1,0) AS flag
FROM ods.ods_hisdb_recipedetail a
LEFT JOIN ods.ods_hisdb_surgeryinfo b
ON a.docnumber = concat('S',b.serialno) and b.tabletype='mid';
方案四:null值转换成随机数
这里使用的随机数,需要保证无法匹配到另外一张表的joinkey。
SELECT
a.serialno,a.recipeno,
if(b.serialno IS NOT NULL AND substr(orderid,1,1)='C',1,0) AS flag
FROM
(SELECT
--这里其实不建议使用*,我们建议只选取外层需要用到的列,减少数据读取返回
*,
CASE WHEN docnumber IS NOT NULL AND trim(docnumber)!='' THEN docnumber ELSE CONCAT('hive',CAST(rand()*100000 AS INT)) END AS docnumber_forjoin
FROM ods.ods_hisdb_recipedetail
) a
LEFT JOIN ods.ods_hisdb_surgeryinfo b
--注意joinkey列也需要对应修改
ON a.docnumber_forjoin = concat('S',b.serialno) and b.tabletype='mid'
WHERE a.tabletype='mid';