数仓|COUNT DISTINCT数据倾斜优化

本文深入探讨了数据倾斜现象,包括其定义、原因及解决方案,并重点介绍了几种常见场景下的优化方法。

什么是数据倾斜

数据倾斜,在MapReduce编程模型中十分常见,就是大量的相同key被分配到一个分区里,造成了个别task运行的非常慢,从而影响了整个任务的执行效率。

数据倾斜产生的根本原因是少数Worker处理的数据量远远超过其他Worker处理的数据量,因此少数Worker的运行时长远远超过其他Worker的平均运行时长,导致整个任务运行时间超长,造成任务延迟。

数据倾斜的原因

当我们看任务进度长时间维持在99%(或100%),查看任务监控页面就会发现只有少量(1个或几个)reduce子任务未完成。因为其处理的数据量和其他reduce差异过大,这就是数据倾斜的直接表现。

导致数据倾斜常见的原因主要包括以下几个方面:

group by倾斜

  • 场景

group bykey分布不均匀。例如,在大促期间,某个店铺的单品PV量达4千万以上,店铺PV量达8千万以上,导致根据商品和店铺的PV量计算IPV时,发生数据倾斜。

  • 解决方案

配置下面的参数与SQL语句一起提交。

set hive.groupby.skewindata=true;

数据倾斜时负载均衡,当选项设定为true,生成的查询计划会有两个MRJob。第一个MRJob 中,Map的输出结果集合会随机分布到Reduce中,每个Reduce做部分聚合操作,并输出结果,这样处理的结果是相同的GroupBy Key有可能被分发到不同的Reduce中,从而达到负载均衡的目的;第二个MRJob再根据预处理的数据结果按照GroupBy Key分布到Reduce中(这个过程可以保证相同的GroupBy Key被分布到同一个Reduce中),最后完成最终的聚合操作。

join倾斜

  • 场景

join onkey分布不均匀。

  • 解决方案

    • 大表join小表

      如果join两边的表中有一张是小表,可以将join改为mapjoin来处理。

    • 关联key中有大量NULL值

      对易产生倾斜的key用单独的逻辑来处理。例如两边表的key中有大量NULL数据会导致倾斜,需要在join前先过滤掉NULL数据或补上随机数,然后再进行join。例如,某张表中,有大量未登录用户的访问记录(user_id为NULL),如果直接和用户表关联的话,会产生倾斜。这时候可以做如下处理:

      SELECT  *
      FROM    table_a a
      LEFT OUTER JOIN table_b b
      ON      CASE WHEN a.user_id IS NULL THEN concat('dp_hive',rand() ) ELSE a.user_id END = b.user_id
      ;
      

      通常情况下,可能倾斜的值不是NULL,而是有意义的数据,这时候就需要对这类数据进行单独处理。

count distinct倾斜

  • 场景

特殊值过多,常见于固定的特殊值比较多的场景,和join中易产生倾斜的key类似。

  • 解决方案

先过滤特殊值,在count结果的基础上加上特殊值的个数。或根据具体场景进行具体分析。

count distinct数据倾斜优化

DISTINCT的使用场景

  • 数据量较小的表

  • 数据量较大时,且使用单个DISTINCT,且GROUP BY字段没有严重的数据倾斜

案例

  • 案例1

统计商品访问的uv

-- 优化前
SELECT  sku_code
        ,COUNT(DISTINCT user_id) AS vst_uv
FROM    dwd_log_vst_di
WHERE   ds = '${cur_date}'
GROUP BY sku_code

-- 优化后
SELECT  sku_code
        ,sum(uv) AS vst_uv
FROM    (
            SELECT  sku_code
                    ,user_id
                    ,1 AS uv
            FROM    dwd_log_vst_di
            WHERE   ds = '${cur_date}'
            GROUP BY sku_code
                     ,user_id
        ) 
GROUP BY sku_code

  • 案例2

统计不同分组下每个标签对应的用户数

假设有一张表dwd_user_tag_df用户标签表

字段注释数据类型
gid分组idSTRING
user_id用户idSTRING
is_tag_a是否是a标签,0:否,1:是BIGINT
is_tag_b是否是b标签,0:否,1:是BIGINT
is_tag_c是否是c标签,0:否,1:是BIGINT
 -- 优化前
SELECT  gid
        ,COUNT(DISTINCT if(is_tag_a = 1,user_id,null)) AS tag_a_usr_cnt
        ,COUNT(DISTINCT if(is_tag_b = 1,user_id,null)) AS tag_b_usr_cnt
        ,COUNT(DISTINCT if(is_tag_c = 1,user_id,null)) AS tag_c_usr_cnt
FROM dwd_user_tag_df
WHERE ds = '${cur_date}'
GROUP BY gid;

-- 优化后

SELECT  gid
        ,SUM(IF(is_a_cnt > 0 ,1,0))  AS a_cnt
        ,SUM(IF(is_b_cnt > 0 ,1,0))  AS b_cnt
        ,SUM(IF(is_c_cnt > 0 ,1,0))  AS ab_cnt
FROM    (
            SELECT  gid
                    ,user_id
                    ,SUM(IF( is_tag_a = 1 ,1,0 )) AS is_a_cnt
                    ,SUM(IF( is_tag_b = 1 ,1,0 )) AS is_b_cnt
                    ,SUM(IF( is_tag_c = 1 ,1,0 )) AS is_c_cnt
            FROM    dwd_user_tag_df
            WHERE   ds = '${cur_date}'
            GROUP BY gid
                     ,user_id
        ) t
GROUP BY gid
;

总结

当数据量较小时,我们几乎遇不到数据倾斜等影响任务运行效率的问题,但是当数据量非常大时且又存在文中所说的使用场景,如果不对任务进行优化,那么很可能任务产出效率会很低,不能满足业务的需求。本文主要介绍引起数据倾斜的几方面原因,并对每种情况进行了说明,最后给出了count distinct数据倾斜优化的案例,希望对你有所帮助。

原文

<think>嗯,用户想解决数据库中的数据倾斜问题。首先,我需要回忆一下数据倾斜的定义和常见原因。根据引用[2],数据倾斜是由于数据分布不平衡,导致某些任务处理时间过长。那在数据库中,常见的操作比如group by、join、count distinct等都可能引发这个问题。 接下来,用户需要解决方案。我需要整理之前学过的应对方法。引用[1]提到随机和扩容表的方法,但这是最后的缓解手段。引用[3]里提到分组聚合倾斜时可以用两阶段聚合,比如加随机前缀。然后对于join操作,可能要用MapJoin或者分桶处理。引用[5]则建议在Map端用distribute by rand()来打乱数据分布,避免某个key过多。 另外,还要考虑空值或无效值的问题,比如过滤掉或者替换这些值。还有参调优,比如开启负载均衡或增加并行度。引用[4]提到合理设计和优化策略能有效缓解问题,所以可能需要结合表设计,比如分桶表或预处理数据。 用户可能需要具体的步骤和例子,比如如何添加随机前缀,或者怎么用distribute by。同时要注意这些方法的应用场景,比如两阶段聚合适用于group by导致的情况,而MapJoin适用于大表和小表关联的情况。 还要考虑是否遗漏其他解决方案,比如动态分区调整或者使用Skew Join参。可能需要检查Hive或Spark的配置参,比如hive.groupby.skewindata或者spark.sql.adaptive.skewJoin.enabled。 最后,确保回答结构清晰,分点列出解决方案,并附上示例代码,比如SQL语句如何修改。同时,引用相关文献来支持每个解决方案的有效性,比如引用[5]提到的distribute by方法。这样用户能一步步理解和应用这些方法,解决数据倾斜问题。</think>在数据库中解决数据倾斜问题,需根据具体场景选择针对性方案。以下是分步骤的解决方案: ### 一、数据倾斜定位与分类 1. 通过监控工具识别长尾任务(如单个Reduce任务耗时超80%总时长) 2. 分析倾斜类型: - 分组聚合倾斜(如$group\ by$字段基小) - 表关联倾斜(如$join$键分布不均) - 特殊值倾斜(如空值/异常值占比过高) ### 二、核心解决方案 #### 1. 分组聚合倾斜 ■ **两阶段聚合法**(适合$group by$场景): ```sql -- 原始语句 SELECT key,count(1) FROM table GROUP BY key; -- 优化后 SELECT split(tmp_key,'_')[0],sum(cnt) FROM ( SELECT key||'_'||floor(rand()*10) as tmp_key,count(1) as cnt FROM table GROUP BY key,floor(rand()*10) ) t GROUP BY split(tmp_key,'_')[0] ``` 通过添加随机前缀实现第一次局部聚合,再全局聚合[^3][^5] #### 2. 表关联倾斜 ■ **MapJoin优化**(适用于大小表关联): ```sql SET hive.auto.convert.join=true; -- 自动转换MapJoin SET hive.mapjoin.smalltable.filesize=25000000; -- 设置小表阈值 ``` ■ **分桶关联法**(适合大表间关联): ```sql CREATE TABLE bucket_table CLUSTERED BY(join_key) INTO 32 BUCKETS; -- 关联时自动触发分桶优化 ``` #### 3. 特殊值处理 ■ **空值随机化**: ```sql SELECT COALESCE(key,concat('null_',rand())) as new_key, value FROM table ``` #### 4. 参调优法 ```sql SET hive.groupby.skewindata=true; -- 分组负载均衡 SET hive.optimize.skewjoin=true; -- 关联倾斜优化 SET hive.skewjoin.key=100000; -- 定义倾斜阈值 ``` ### 三、进阶解决方案 1. **动态扩容法**(终极方案): - 对倾斜Key进行单独扩容(如10倍扩容) - 使用随机打散分布[^1] 2. **预处理法**: ```sql -- 创建中间汇总表 CREATE TABLE mid_table AS SELECT key,count(1) as cnt FROM src_table GROUP BY key; -- 后续查询直接使用汇总结果 ``` ### 四、预防措施 1. 设计阶段采用分桶表结构 2. 建立数据质量监控体系 3. 对常用关联字段提前做基分析 4. 使用列式存储格式(ORC/Parquet) 通过上述方法,可有效缓解$join$操作中因数据分布不均导致的Reduce阶段长尾问题[^2][^4]。实际应用中建议先进行小规模测试,逐步验证优化效果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值