spark sql中group by倾斜表现为少数task执行极慢、shuffle read size异常放大、gc时间飙升;根本原因是key分布不均,需用salt+两阶段聚合打散key,同时过滤或重映射空值,并采样定位倾斜key,启用自适应优化时需谨慎配置。

Spark SQL中group by倾斜的典型表现
执行GROUP BY后,大部分task几秒完成,但1–2个task卡住十几分钟甚至OOM;Spark UI里能看到某个task的Shuffle Read Size是其他task的百倍以上;Executor GC Time占比突然飙升到30%+。这不是资源不够,是key分布本身不均——比如日志表里user_id = ''占了40%,或某商品ID在点击流中出现频次超均值500倍。
用salt + 两阶段聚合绕过单点瓶颈
直接加spark.sql.shuffle.partitions没用:同一个key永远进同一个partition,调高并行度只是让“巨无霸partition”被拆得更细,但数据还在那儿。必须打散key本身。
- 第一阶段:给倾斜key加随机前缀(“加盐”),把
GROUP BY key变成GROUP BY concat(rand(10), '_', key),再聚合 - 第二阶段:去掉前缀,对结果再
GROUP BY key汇总 - 随机数范围别硬写10——观察倾斜程度,若最热key占全量30%,建议用50–100;若占70%以上,至少200+
SQL示例:
SELECT key, SUM(cnt) AS total_cnt
FROM (
SELECT
SPLIT(agg_key, '_')[1] AS key,
SUM(cnt) AS cnt
FROM (
SELECT
CONCAT(CAST(FLOOR(RAND() * 100) AS STRING), '_', key) AS agg_key,
COUNT(*) AS cnt
FROM your_table
GROUP BY CONCAT(CAST(FLOOR(RAND() * 100) AS STRING), '_', key)
)
GROUP BY SPLIT(agg_key, '_')[1]
)
空值/默认值必须提前过滤或重映射
NULL、空字符串、'0'、'-1'这类值在hash分区时全落到同一partition,极易成为隐形热点。不能依赖业务方“保证数据干净”。
- 明确过滤:
WHERE key IS NOT NULL AND key != '' AND key != '0' - 或重映射为带业务含义的伪key:
COALESCE(key, CONCAT('unknown_', FLOOR(RAND() * 1000))),避免所有空值挤在一起 - 注意:如果空值本身有分析价值(如统计游客行为),就别过滤,改用重映射+后续单独聚合
聚合前先采样定位真实倾斜key
别猜,用TABLESAMPLE快速探查:
SELECT key, COUNT(*) AS freq FROM your_table TABLESAMPLE (1) GROUP BY key ORDER BY freq DESC LIMIT 10
如果前3个key占比超总样本60%,基本可确认是它们导致倾斜。这时再决定:是加盐、过滤、还是走预聚合(比如先在Hive里按天+key聚合好)。
真正容易被忽略的是:加盐后必须同步调整spark.sql.adaptive.enabled和spark.sql.adaptive.coalescePartitions.enabled——自适应查询优化器可能把打散后的中间结果又强行合并,让盐失效。










