更新时间:2026-10-20 GMT+08:00
分享

Group By

一个带Group By的伪代码示例如下。

SELECT  shop_id
       ,sum(is_open) AS 营业天数
FROM    table_xxx_di
WHERE   dt BETWEEN '${bizdate_365}' AND '${bizdate}'
GROUP BY shop_id;

当发生数据倾斜时,可以通过如下两种方案解决:

序号

方案

说明

方案一

添加随机数

把引起长尾的key进行拆分。

方案二

创建滚存表

降本提效。

方案一:添加随机数。

对SQL进行改写,添加随机数,把引起长尾的key进行拆分是解决Group By长尾的一种比较好的方法。

SELECT Key, Count(*) AS Cnt FROM TableName GROUP BY Key;

不考虑Combiner,M节点会Shuffle到R节点上,然后R节点再做Count操作,对应的执行计划是M->R。

假定已经找到了引起长尾的key,对长尾的key再做一次工作再分配,就变成:

-- 假设长尾的Key已经找到是KEY001
SELECT  a.Key
       ,SUM(a.Cnt) AS Cnt
FROM (
    SELECT  Key
           ,COUNT(*) AS Cnt
    FROM    TableName
    GROUP BY Key
           ,CASE WHEN Key = 'KEY001' THEN pmod(hash(rand()), 50)
                 ELSE 0
            END
) a
GROUP BY a.Key;

改完之后的执行计划变成了 M->R->R,虽然执行步骤变长了,但是长尾的key经过了 2 个步骤的处理,整体的时间消耗可能反而有所减少。与不加随机数的直接聚合相比,资源消耗与耗时效果基本持平;但实际场景中引发长尾的key不止一个,再考虑寻找长尾key和SQL改写的投入成本,直接对长尾key添加随机数(不做两阶段改写)的投入成本会更低一些。

方案二:创建滚存表。

降本提效,核心诉求是取过去一年的商户数据,对于线上任务而言,每次都要读取T-1至T-365的所有分区其实是对资源的很大浪费,创建滚存表可以减少分区的读取但是又不影响过去一年的取数,示例如下。

首次初始化365天的商户营业数据(Group By汇总),标记数据更新日期,记为表a;后续线上任务切换为T-2日表a关联table_xxx_di表再Group By,这样每天读取的数据从365减少到了2个,主键shopid的重复性极大降低,对资源的消耗也会减少。

-- 创建滚存表
CREATE TABLE IF NOT EXISTS m_xxx_365_df (
   shop_id STRING COMMENT '商户ID',
   last_update_ds STRING COMMENT '最近更新日期',
   `365d_open_days` BIGINT COMMENT '365天营业天数'
)
PARTITIONED BY (ds STRING COMMENT '日期分区');

-- 假定 365d 是 2021.5.1-2022.5.1,先完成一次初始化
INSERT OVERWRITE TABLE m_xxx_365_df PARTITION(ds = '20220501')
SELECT shop_id
      ,max(ds) AS last_update_ds
      ,sum(is_open) AS `365d_open_days`
FROM table_xxx_di
WHERE ds BETWEEN '20210501' AND '20220501'
GROUP BY shop_id;

-- 那么之后线上任务要执行的是
INSERT OVERWRITE TABLE m_xxx_365_df PARTITION(ds = '${bizdate}')
SELECT aa.shop_id
      ,aa.last_update_ds
      ,`365d_open_days` - COALESCE(is_open, 0) AS `365d_open_days`  -- 消除营业天数的无限滚存
FROM (
    SELECT shop_id
          ,max(last_update_ds) AS last_update_ds
          ,sum(`365d_open_days`) AS `365d_open_days`
    FROM (
        SELECT shop_id
              ,ds AS last_update_ds
              ,sum(is_open) AS `365d_open_days`
        FROM table_xxx_di
        WHERE ds = '${bizdate}'
        GROUP BY shop_id
        UNION ALL
        SELECT shop_id
              ,last_update_ds
              ,`365d_open_days`
        FROM m_xxx_365_df
        WHERE ds = '${bizdate_2}' AND last_update_ds >= '${bizdate_365}'
        GROUP BY shop_id
    )
    GROUP BY shop_id
) AS aa
LEFT JOIN (
    SELECT shop_id, is_open
    FROM table_xxx_di
    WHERE ds = '${bizdate_366}'
) AS bb
ON aa.shop_id = bb.shop_id;

相关文档