先执行SELECT key, COUNT(*) AS cnt FROM tbl GROUP BY key ORDER BY cnt DESC LIMIT 10定位倾斜key,若top1的cnt占全表20%以上或远超其他值(如差两个数量级),即为真凶;建议先抽样或查统计信息避免查询本身倾斜。

怎么确认是哪个key在拖慢GROUP BY
别急着加盐或调参数,先定位真凶。执行 SELECT key, COUNT(*) AS cnt FROM tbl GROUP BY key ORDER BY cnt DESC LIMIT 10,看前几行的 cnt 是否远超其他值(比如相差两个数量级以上)。如果 top1 的 cnt 占全表 20% 以上,基本就是它了。注意:这个查询本身也可能倾斜,建议先对表做 TABLESAMPLE(0.1) 抽样估算,或用 ANALYZE TABLE 后查统计信息。
对已知倾斜key加盐聚合(最常用有效)
核心思路是把一个大key拆成多个子key,在map端分散计算,再合并结果。假设已知倾斜key是 'user_0',且你希望拆成最多 100 份:
SELECT
key,
SUM(cnt) AS total_cnt
FROM (
SELECT
CASE WHEN key = 'user_0' THEN CONCAT(key, '_', CAST(FLOOR(RAND() * 100) AS STRING)) ELSE key END AS key,
COUNT(*) AS cnt
FROM tbl
GROUP BY
CASE WHEN key = 'user_0' THEN CONCAT(key, '_', CAST(FLOOR(RAND() * 100) AS STRING)) ELSE key END
) t
GROUP BY key关键点:
- RAND() 必须在 GROUP BY 和 SELECT 中保持一致,否则会出错
- 盐值范围(这里是 100)不能太小,否则仍可能聚集;也不能太大,否则小key也被打散,增加shuffle量
- 如果有多个倾斜key,用 WHEN key IN ('a', 'b', 'c') 扩展条件即可
用adaptive execution自动处理倾斜(Spark 3.2+)
如果你用的是 Spark 3.2 或更高版本,开启自适应执行比手写加盐更省事,尤其适合不确定哪些key会倾斜的场景:
- 设置
spark.sql.adaptive.enabled=true - 必须同时开
spark.sql.adaptive.skewedJoin.enabled=true(虽然叫skewedJoin,但它也管GROUP BY倾斜) - 调大
spark.sql.adaptive.skewedPartitionMaxSplits=10(默认是 5,对严重倾斜不够用)
注意:这个机制只在 shuffle 阶段动态检测到倾斜 partition 后才触发拆分,不会影响无倾斜的正常路径;但要求中间结果能被重分区,所以 cache 或 checkpoint 可能干扰其生效。
为什么加盐后count结果变多了
这是最容易踩的坑:加盐只改了临时分组逻辑,没改原始语义。如果你最终要的是 COUNT(DISTINCT user_id) 这类指标,直接套用上面的加盐模板会重复计数。正确做法是——先对原key去重,再加盐:
SELECT
key,
SUM(distinct_users) AS total_distinct_users
FROM (
SELECT
CASE WHEN key = 'user_0' THEN CONCAT(key, '_', CAST(FLOOR(RAND() * 100) AS STRING)) ELSE key END AS salted_key,
key,
COUNT(DISTINCT user_id) AS distinct_users
FROM tbl
GROUP BY
CASE WHEN key = 'user_0' THEN CONCAT(key, '_', CAST(FLOOR(RAND() * 100) AS STRING)) ELSE key END,
key
) t
GROUP BY key本质是把“去重”提前到局部聚合阶段,避免全局去重时再集中到一个task。业务上真正难的不是写SQL,而是判断哪些key该加盐、加多少、是否需要保留原始语义——这些没法靠配置自动推断,得翻日志、看监控、查业务逻辑。


















