group by倾斜和join倾斜是两个独立问题,但多表关联后group by倾斜更难定位——真正拖慢的不是group by本身,而是join阶段已将热点key数据量放大数倍,再进group by则雪上加霜。

直接结论:GROUP BY 倾斜和 JOIN 倾斜是两个独立问题,但多表关联后 GROUP BY 倾斜更难定位——真正拖慢的不是 GROUP BY 本身,而是 JOIN 阶段已把热点 key 的数据量放大数倍,再进 GROUP BY 就雪上加霜。
先确认倾斜发生在 JOIN 还是 GROUP BY 阶段
Spark SQL 3.4 默认开启自适应查询执行(AQE),但 spark.sql.adaptive.enabled 不会自动修复逻辑层倾斜。必须分阶段验证:
- 单独跑
SELECT COUNT(*) FROM t1 JOIN t2 ON ...,看 shuffle write 最大的 stage 是否在 JOIN; - 再跑
SELECT key, COUNT(*) FROM (t1 JOIN t2 ...) GROUP BY key ORDER BY COUNT(*) DESC LIMIT 5,抽样看 key 分布是否极不均匀(如 top1 cnt > top2 × 10); - 如果 JOIN 后数据量暴涨且 key 分布陡峭,说明 JOIN 已放大倾斜,GROUP BY 只是“最后一根稻草”。
对 JOIN 阶段已确认的热点 key 加盐(Spark SQL 3.4 兼容写法)
不能用 RAND()(无 seed 会导致每次结果不可复现),也不能用 CONCAT(key, '_', RAND(123))(同一行多次调用仍可能不一致)。正确做法是:
- 用
CASE WHEN key = 'hot_key' THEN CONCAT('s', FLOOR(RAND(123) * 20), '_', key) ELSE key END对倾斜 key 打散; - 盐值范围选 20–50,太小(如 2)无法缓解,太大(如 200)会显著增加 shuffle 数据量;
- 必须确保 JOIN 和后续 GROUP BY 使用**完全相同的盐逻辑**,否则关联断裂或聚合错乱。
两阶段聚合:局部打散 + 全局还原(适用于 GROUP BY 维度固定场景)
当 JOIN 后仍需按原始 key 聚合,且已知几个高频 key(如 'user_id = 0', 'user_id IS NULL'),可手动拆解:
SELECT key, SUM(cnt) AS total
FROM (
-- 非热点 key 正常聚合
SELECT COALESCE(user_id, '-1') AS key, COUNT(*) AS cnt
FROM joined_table
WHERE user_id NOT IN ('0', '') AND user_id IS NOT NULL
GROUP BY COALESCE(user_id, '-1')
<p>UNION ALL</p><p>-- 热点 key 单独加盐聚合
SELECT
CASE
WHEN user<em>id = '0' THEN CONCAT('salt</em>', FLOOR(RAND(123) <em> 30), '_0')
WHEN user<em>id IS NULL THEN CONCAT('salt</em>', FLOOR(RAND(123) </em> 30), '_null')
ELSE user_id
END AS key,
COUNT(<em>) AS cnt
FROM joined_table
WHERE user_id IN ('0', '') OR user_id IS NULL
GROUP BY
CASE
WHEN user<em>id = '0' THEN CONCAT('salt</em>', FLOOR(RAND(123) </em> 30), '_0')
WHEN user<em>id IS NULL THEN CONCAT('salt</em>', FLOOR(RAND(123) * 30), '_null')
ELSE user_id
END
)
GROUP BY key</p>
注意:子查询中 RAND(123) 必须显式带 seed,且两次出现位置需保持一致;最终 GROUP BY key 会把所有 salt_xxx_0 归为一个逻辑 key,但前提是 salt 逻辑完全对齐。
容易被忽略的关键点
Spark SQL 3.4 中,spark.sql.adaptive.skewedJoin.enabled=true 仅对 **JOIN** 生效,对 GROUP BY 无效;hive.groupby.skewindata=true 在 Spark SQL 中不识别;NULL 值在 JOIN ON 条件中默认不匹配,但进入 GROUP BY 后会被强制归为一组——所以 COALESCE 或 NULL 单独分支处理,比加盐更轻量也更安全。










