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

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。

      优先推荐开启AQE Skew Join,配置参数如下:

      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

相关文档