确认join导致长尾需先查日志:若spark ui或hive logview中某task的shuffle read量(如5gb)远超均值(20mb)、耗时超10分钟且其余task早已完成,再结合select key,count() from big_table group by key order by count() desc limit 5发现前5名key占比超60%,即可判定为join倾斜。

怎么确认是 JOIN 导致的长尾?
别急着改 SQL,先看日志里哪个 stage 卡住了。Spark UI 或 Hive LogView 中,如果某个 Shuffle Read 量远超其他 task(比如 5GB vs 平均 20MB),且该 task 运行时间 >10 分钟、其余早已完成,基本就是 JOIN 倾斜。重点盯 JOIN 条件字段的 key 分布——执行 SELECT key, COUNT(*) FROM big_table GROUP BY key ORDER BY COUNT(*) DESC LIMIT 5,看看前几名是不是占了全表 60%+ 记录。
为什么不能直接加盐,而要手动拆分热点?
加盐(如 CONCAT(key, '_', FLOOR(RAND()*10)))适合“少量极高频 key”,但若热点 key 本身语义强(比如 user_id = '0' 表示未登录用户)、或下游依赖原始 key 做业务判断,加盐后二次聚合易出错。手动拆分更可控:把已知的 top N 热点 key 单独拎出来,用 MapJoin 处理;其余走常规 Shuffle Join。这样既避免随机性引入的不可控膨胀,又保留原始 key 的可解释性。
手动拆分的三步实操要点
以大表 fact_order 关联小维表 dim_user,发现 user_id IN ('0', '999999') 是热点:
- 先建临时热点清单:
CREATE TABLE tmp_hot_users AS SELECT '0' AS user_id UNION ALL SELECT '999999' - 主表拆成两路:热点部分用
/*+ MAPJOIN(dim) */强制广播维表;非热点部分正常JOIN,注意 WHERE 排除已处理的 key:WHERE user_id NOT IN ('0', '999999') - 最后
UNION ALL合并结果——别用UNION,去重开销大,且两路数据天然不重叠
容易被忽略的坑:维表也要同步拆分
只拆主表没用。维表 dim_user 中,user_id = '0' 对应的记录可能有上万条(比如默认头像、空昵称等脏数据)。必须同步按 user_id IN ('0', '999999') 抽出子集,否则 MapJoin 时维表膨胀仍会触发 OOM。更稳妥的做法是:对热点维表记录提前做轻量聚合,比如把多条地址合并为 ARRAY,用 STRUCT 承载,减少广播体积。











