优先用aqe(spark.sql.adaptive.enabled=true且spark.sql.adaptive.skewedjoin.enabled=true),它对group by生效且不改sql;若aqe不可用,则手动加盐两阶段聚合,需确保rand()在group by和select中严格一致,并合理设置随机范围(如*20)。

Spark SQL 3.5 中处理倾斜键的分组聚合,优先用 spark.sql.adaptive.enabled=true + spark.sql.adaptive.skewedJoin.enabled=true,它对 GROUP BY 同样生效,且无需改 SQL;若需精准控制或 AQE 不可用,则必须手动加盐,但要注意 RAND() 在 GROUP BY 和 SELECT 中必须完全一致。
怎么快速定位哪个 key 正在拖慢 GROUP BY
别猜,直接跑诊断 SQL:
SELECT key, COUNT(*) AS cnt FROM tbl GROUP BY key ORDER BY cnt DESC LIMIT 20
看前几行的 cnt 是否远超其余(比如其他 key 是 1k~5k,某个 device_id 突然跳到 300 万),这就是倾斜 key。Spark UI 上“SQL”页签里某个 task 耗时明显长、executor 报 java.lang.OutOfMemoryError 或卡在 99%,都是现象,根源一定落在具体 key 上。
手动加盐做两阶段聚合时,RAND() 必须严格同步
RAND() 每次调用都生成新值,如果在 GROUP BY 子句里算一次,在 SELECT 里又算一次,同一个原始 key 可能被拆成两个不同盐值,导致漏统计。
正确写法要点:
- 盐值计算只写一次,用
CASE WHEN包裹后在GROUP BY和外层SELECT中复用 - 随机范围建议设为 10~50(如
FLOOR(RAND() * 20)),太小打散不足,太大让小 key 也被误拆、增加 shuffle 量 - 多个倾斜 key 可合并判断:
WHEN key IN ('k1', 'k2', 'k3'),避免嵌套过深 - 盐前缀推荐用下划线连接(
CONCAT(key, '_', ...)),避免和原始 key 冲突(比如 key 本身含数字)
为什么不能只靠调大 spark.sql.shuffle.partitions
把 spark.sql.shuffle.partitions 从 200 改到 2000,只是让每个分区平均数据量变小,但倾斜 key 仍全部落在同一分区——它占全量 80%,那 2000 个分区里,仍有约 1600 个空转,剩下 400 个争抢处理这 80% 数据。资源浪费没缓解,task 最大耗时也没降。这是典型“治标不治本”,只能作为辅助手段配合加盐或 AQE 使用。
Spark 3.5 的 AQE 自动倾斜处理要开对参数
AQE 在 3.5 中已稳定支持 GROUP BY 倾斜检测与分裂,但有两个关键点常被忽略:
- 必须同时启用:
spark.sql.adaptive.enabled=true且spark.sql.adaptive.skewedJoin.enabled=true(名字带 Join,但实际覆盖Aggregate类 operator) -
spark.sql.adaptive.skewedPartitionMaxSplits默认是 5,对严重倾斜不够用,建议设为 10;超过 10 会显著增加调度开销,不建议盲目调高 - 作业中若显式用了
DISTRIBUTE BY或禁用了 AQE(如spark.sql.adaptive.enabled=false),该机制就完全失效
真正复杂的不是方案本身,而是判断:AQE 适合临时分析或分布不可预知的场景;而加盐必须知道倾斜 key —— 如果业务上根本没法提前识别(比如每天新增的爆款商品 ID),就得靠采样 + 动态过滤 + 加盐组合落地,这时候 RAND() 同步和盐范围校准就容不得半点马虎。










