SQL如何防止数据倾斜(来自: 渭河数据分析社区)
在Hive这类分布式计算框架里,数据分布不均匀,导致某些节点处理的数据量远大于其他节点,进而影响整体性能。例如,就像10个人分100个苹果,其中9个人各拿1个苹果,剩下1个人要拿91个。这时候拿91个的人会花很长时间,等他吃完其他人才开始吃,最后整体完成时间完全取决于这个最慢的人。习惯1:写SQL前先做「数据体检」FROM tableLIMIT 10;-- 看TOP10的数据量是否均衡看空值:用S
1、什么是数据倾斜?
在Hive这类分布式计算框架里,数据分布不均匀,导致某些节点处理的数据量远大于其他节点,进而影响整体性能。
例如,就像10个人分100个苹果,其中9个人各拿1个苹果,剩下1个人要拿91个。这时候拿91个的人会花很长时间,等他吃完其他人才开始吃,最后整体完成时间完全取决于这个最慢的人。
2、具体表现?
1)看执行日志像看心电图
执行计划中某个reduce节点(通常是编号末尾带"r=0"的那个)的进度条会卡在99%长时间不动,其他节点早就显示"已完成"
2)资源利用率两极分化
90%的节点CPU利用率不到10%,而个别节点占用全部内存还报错(常见OOM Killer被触发)
3)时间线诡异
任务总时长显示"10分钟",但前9分50秒所有节点都处于空闲状态,最后20秒突然疯狂滚动日志
4)数据分布可视化
如果用SELECT key, COUNT(*) FROM table GROUP BY key统计,会发现某些key对应的数值比其他大几十甚至上百倍
3、典型场景举例
1)场景一:热点数据倾斜(比如北京的数据特别多)
-- 假设有一个全国用户订单表,北京的数据占70%
SELECT province, COUNT(*)
FROM user_orders
GROUP BY province;
现象:执行时发现北京的数据量是其他省份的几十倍,导致处理北京数据的任务卡死
解决方案:
方法一:提前过滤热点(把大石头单独搬走)
-- 先处理北京的数据
SELECT province, COUNT(*)
FROM user_orders
WHERE province = 'Beijing';
-- 再处理其他省份(用NOT IN排除热点)
SELECT province, COUNT(*)
FROM user_orders
WHERE province != 'Beijing'
GROUP BY province;
方法二:随机数打散(把大石头砸成小石子)
-- 给北京的数据加上随机后缀(比如分10个小任务)
SELECT
CASE WHEN province = 'Beijing'
THEN CONCAT(province, '_', FLOOR(RAND()*10))
ELSE province
END AS new_province,
COUNT(*)
FROM user_orders
GROUP BY new_province;
场景2:空值/默认值倾斜(比如null值太多)
-- 用户地址表中,80%的地址字段是NULL
SELECT address, COUNT(*)
FROM user_address
GROUP BY address;
现象:处理NULL值的任务堆积如山,其他地址任务秒完成
解决方案:
方法:给空值加盐(把垃圾袋拆分成多个小袋)
-- 把NULL变成多个随机值(比如null_1, null_2...)
SELECT
COALESCE(address, CONCAT('null_', CAST(FLOOR(RAND()*5) AS STRING))) AS new_address,
COUNT(*)
FROM user_address
GROUP BY new_address;
场景3:JOIN关联倾斜(比如大V用户关联时卡死)
-- 用户表(user)有1亿条数据,其中用户ID=123的超级用户有100万条行为数据
-- 行为表(action)有10亿条数据,超级用户的行为占10%
SELECT u.user_id, COUNT(a.action_id)
FROM user u
JOIN action a ON u.user_id = a.user_id
GROUP BY u.user_id;
现象:JOIN时处理用户ID=123的任务卡在99%,其他任务早就完成
解决方案:
方法1:MapJoin广播小表(把字典直接发给所有人)
-- 如果用户表比较小(比如<2G),直接强制广播
SET hive.auto.convert.join=true; -- 默认开启
SET hive.mapjoin.smalltable.filesize=20000000; -- 小表阈值
SELECT /*+ MAPJOIN(u) */
u.user_id, COUNT(a.action_id)
FROM user u
JOIN action a ON u.user_id = a.user_id
GROUP BY u.user_id;
方法2:打散大Key(把大象切成牛排)
-- 给大V用户的ID加上随机前缀(比如0~9)
-- 行为表也做同样处理,让数据分散到不同任务
SELECT
u.user_id,
SUM(a.cnt)
FROM
(SELECT user_id,
CASE WHEN user_id = '123'
THEN CONCAT(user_id, '_', FLOOR(RAND()*10))
ELSE user_id
END AS join_key
FROM user) u
JOIN
(SELECT user_id,
CASE WHEN user_id = '123'
THEN CONCAT(user_id, '_', FLOOR(RAND()*10))
ELSE user_id
END AS join_key,
COUNT(*) AS cnt
FROM action
GROUP BY user_id, join_key) a
ON u.join_key = a.join_key
GROUP BY u.user_id;
4、通用处理步骤(急救包)
1)先定位哪里倾斜:
- 执行GROUP BY看哪个Key的数据量爆炸(哪张表引用的数据最多)
- 看Hive日志里哪个任务卡在99%
2)简单处理三板斧:
- 过滤热点:用WHERE把大Key单独处理
- 打散数据:给大Key加随机数(RAND()或FLOOR(RAND()*N))
- 调参数:临时加大内存(set mapreduce.reduce.memory.mb=8192;)
5、总结
习惯1:写SQL前先做「数据体检」
查分布:任何涉及GROUP BY/JOIN的字段,先跑一次:
SELECT key_column, COUNT(*) AS cnt
FROM table
GROUP BY key_column
ORDER BY cnt DESC
LIMIT 10; -- 看TOP10的数据量是否均衡
看空值:用SUM(CASE WHEN column IS NULL THEN 1 ELSE 0 END)统计空值比例
防爆雷:如果发现某个key占比超过30%,立刻警惕倾斜风险
习惯2:学会「拆快递式」处理数据
分治思想:遇到大key时主动拆分
-- 原写法(危险):
SELECT * FROM logs WHERE user_id = '超级用户';
-- 安全写法(拆成10份):
SELECT * FROM logs
WHERE user_id = '超级用户'
AND SUBSTR(时间戳, -1) IN ('0','1','2'); -- 按时间尾数分批查
多用采样:测试阶段用TABLESAMPLE(BUCKET 1 OUT OF 100)随机取1%数据验证逻辑
习惯3:给常用SQL加「安全气囊」
配置参数:在Hive脚本开头养成设置这些参数的习惯
-- 自动处理倾斜的JOIN
SET hive.optimize.skewjoin=true; -- 开启倾斜JOIN优化
SET hive.skewjoin.key=100000; -- 超过10万行的key视为倾斜
-- 增加并行度
SET hive.exec.reducers.bytes.per.reducer=256000000; -- 每个reduce处理256MB
SET hive.exec.reducers.max=200; -- 最多开200个reduce
-- 内存保护
SET mapreduce.reduce.memory.mb=4096; -- 调大reduce内存
习惯4:学会「看红绿灯」——监控任务日志
关键观察点:
- 进度条:如果某个reduce卡在99%超过5分钟,大概率倾斜
- 资源监控:在YARN界面看哪个container内存/CPU爆红
- 数据分布:执行完成后用desc table extended看各分区大小是否均衡
习惯5:设计表时「提前分篮子」
预防性设计:
分区策略:对常作为过滤条件的字段(如日期、省份)建立分区(取数的时候也针对性的取出分区)
分桶表:对高频JOIN的字段(如user_id)用CLUSTERED BY分桶
CREATE TABLE user_orders
CLUSTERED BY (user_id) INTO 100 BUCKETS -- 强制均匀分布
避免全表扫描:永远不在WHERE条件中使用无分区过滤的裸查!!!
习惯6:掌握「三大保命口诀」
遇到倾斜问题时按顺序尝试:
- 「能过滤就不计算」:先用WHERE剔除大key
- 「打不过就分家」:用RAND()或MOD()拆分倾斜数据
- 「打不过就摇人」:超过30分钟解决不了,立刻找技术老司机求助

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐
所有评论(0)