针对 null 值计算倾斜的处理

  SQL报错
内容纲要

概要描述

本文主要介绍 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)

file

检查左表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;

file

检查右表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;

file

方案一:改写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内容不需要做修改)

file

方案三:强制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';

这篇文章对您有帮助吗?

平均评分 0 / 5. 次数: 0

尚无评价,您可以第一个评哦!

非常抱歉,这篇文章对您没有帮助.

烦请您告诉我们您的建议与意见,以便我们改进,谢谢您。