[Hive]Hive数据倾斜(大表join大表)

本文详细介绍了在处理GB级数据时,优化大数据表左关联性能的过程。通过三次优化策略,成功将耗时从1.5小时缩短至1分钟32秒。首次优化调整数据类型匹配,第二次优化排除无效关联,第三次优化通过预处理无效字段,实现高效数据关联。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

业务背景

用户轨迹工程的性能瓶颈一直是etract_track_info,其中耗时大户主要在于trackinfo与pm_info进行左关联的环节,trackinfo与pm_info两张表均为GB级别,左关联代码块如下:

from trackinfo a 
left outer join pm_info b 
on (a.ext_field7 = b.id) 

使用以上代码块需要耗时1.5小时。

优化流程

第一次优化

考虑到pm_info表的id是bigint类型,trackinfo表的ext_field7是string类型,其关联时数据类型不一致,默认的hash操作会按bigint型的id进行分配,这样会导致所有string类型的ext_field7集中到一个reduce里面,因此,改为如下:

from trackinfo a 
left outer join pm_info b 
on (cast(a.ext_field7 as bigint) = b.id) 

改动为上面代码后,效果仍然不理想,耗时为1.5小时。

第二次优化

考虑到trackinfo表的ext_field7字段缺失率很高(为空、字段长度为零、字段填充了非整数)情况,做进行左关联时空字段的关联操作实际上没有意义,因此,如果左表关联字段ext_field7为无效字段,则不需要关联,因此,改为如下:

from trackinfo a 
left outer join pm_info b 
on (a.ext_field7 is not null 
and length(a.ext_field7) > 0 
and a.ext_field7 rlike '^[0-9]+$' 
and a.ext_field7 = b.id)

上面代码块的作用是,如果左表关联字段ext_field7为无效字段时(为空、字段长度为零、字段填充了非整数),不去关联右表,由于空字段左关联以后取到的右表字段仍然为null,所以不会影响结果。
改动为上面代码后,效果仍然不理想,耗时为50分钟。

第三次优化

想了很久,第二次优化效果效果不理想的原因,其实是在左关联中,虽然设置了左表关联字段为空不去关联右表,但是这样做,左表中未关联的记录(ext_field7为空)将会全部聚集在一个reduce中进行处理,体现为reduce进度长时间处在99%。
换一种思路,解决办法的突破点就在于如何把左表的未关联记录的key尽可能打散,因此可以这么做:若左表关联字段无效(为空、字段长度为零、字段填充了非整数),则在关联前将左表关联字段设置为一个随机数,再去关联右表,这么做的目的是即使是左表的未关联记录,它的key也分布得十分均匀

from trackinfo a 
left outer join pm_info b 
on (
    case when (a.ext_field7 is not null 
        and length(a.ext_field7) > 0 
        and a.ext_field7 rlike '^[0-9]+$') 
    then 
        cast(a.ext_field7 as bigint) 
    else 
        cast(ceiling(rand() * -65535) as bigint) 
    end = b.id
) 

第三次改动后,耗时从50分钟降为了1分钟32秒,效果显著!

<think>我们正在解决Hive数据倾斜导致的与小Join性能问题。根据引用内容,我们可以总结出以下解决方案: 1. **Map Join(小足够小)**:当小小不超过一定阈值(默认25MB)时,可以开启Map Join,将小广播到所有Map任务的内存中,从而避免Reduce阶段,也就避免数据倾斜。 - 设置参数:`set hive.auto.convert.join=true;` 和 `set hive.mapjoin.smalltable.filesize=25000000;`(可根据实际情况调整小小阈值) 2. **打散小扩容(小但不超过100MB)**:如果小超过了默认的Map Join阈值,但小不超过100MB,可以强制执行Map Join(引用[3])。 - 使用提示:在查询中添加`/*+ MAPJOIN(small_table) */`,例如:`select /*+ MAPJOIN(b) */ a.key, a.value, b.value from big_table a join small_table b on a.key = b.key` 3. **打散小扩容(小,超过Map Join阈值)**:引用[1]中提到,如果小比较,可以采用“打散小扩容”的方法。具体做法是将小复制多份,并将数据按照某个字段打散(例如,给的每条记录加上一个随机前缀),然后将小也进行相应的扩容(例如,将小的每条记录复制多份,每份对应一个随机前缀),这样可以将原本可能倾斜的key分散到不同的reduce任务中。 - 具体步骤(以引用[1]中的例子为例): - 假设A,小B,关联字段为key。 - 给A的每条记录添加一个随机前缀(0到n-1,n为扩容倍数,比如10),这样key变成`concat(rand() % n, key)`。 - 将小B扩容n倍,即每条记录复制n份,并为每条记录添加一个固定的前缀(0到n-1),这样key变成`concat(i, key)`(i从0到n-1)。 - 然后进行关联:`A_mod.key = B_mod.key`,这样原本同一个key的数据会被分散到不同的reduce中。 4. **过滤倾斜key**:如果中存在少数几个key的数据量特别,可以考虑先过滤掉这些倾斜的key,单独处理,然后再合并结果(引用[2]中提到的方法)。 5. **Skew Join**:Hive提供了Skew Join优化(引用[2]),通过设置参数`hive.optimize.skewjoin=true`和`hive.skewjoin.key`(默认100000)来启用。当某个key的记录数超过设定的阈值时,Hive会将该key的数据随机分发到多个Reduce任务中,从而避免单个Reduce任务处理过多数据。 6. **减少关联字段的数据量**:引用[2]中提到,如果关联,但实际只需要关联部分数据(比如只需要当天有行为的用户),可以先对其中一个进行去重,然后再关联,从而减少关联数据量。 针对与小Join,最常用的是Map Join(当小较小时)和打散扩容(当小时)。下面我们详细说明这两种方法: ### 解决方案1:Map Join(小足够小) 如果小小在Hive配置的Map Join阈值内(默认25MB),则直接开启Map Join。 ```sql -- 设置参数 set hive.auto.convert.join=true; -- 开启自动Map Join转换 set hive.mapjoin.smalltable.filesize=25000000; -- 设置小小阈值(25MB) -- 执行查询 select /*+ MAPJOIN(small_table) */ big_table.id, big_table.data, small_table.info from big_table join small_table on big_table.key = small_table.key; ``` ### 解决方案2:打散小扩容(小) 当小超过Map Join阈值,但又不是特别(比如几百MB)时,我们可以使用打散扩容的方法。 假设我们设定一个扩容倍数n(例如10): 1. 将打散:为每个key添加一个随机前缀(0到n-1) 2. 将小扩容:复制n份,并为每份的key添加固定的前缀(0到n-1) ```sql -- 设置参数(可选,根据情况调整) set hive.exec.reducers.bytes.per.reducer=1000000000; -- 控制每个reducer处理的数据量 -- 打散,添加随机前缀 create temporary view big_table_mod as select concat(cast(floor(rand() * 10) as string), '_', key) as mod_key, -- 添加0-9的随机前缀 id, data from big_table; -- 小扩容10倍,添加固定前缀 create temporary view small_table_mod as select concat(cast(num as string), '_', key) as mod_key, key, info from small_table lateral view explode(array(0,1,2,3,4,5,6,7,8,9)) nums as num; -- 复制10份,每份添加固定前缀 -- 关联打散后的和小 select split(b.mod_key, '_')[1] as key, -- 去掉前缀,恢复原始key b.id, b.data, s.info from big_table_mod b join small_table_mod s on b.mod_key = s.mod_key; ``` 注意:这种方法会增加小数据量(n倍),所以n不能太,否则小会变得过。同时,数据量不变,但增加了随机前缀,使得数据被分散到不同的reduce中。 ### 解决方案3:Skew Join(针对中有倾斜key) 如果中存在倾斜的key(即某些key的数据量特别),可以尝试使用Hive的Skew Join。 ```sql -- 设置参数 set hive.optimize.skewjoin=true; -- 开启Skew Join set hive.skewjoin.key=100000; -- 设置倾斜key的阈值(超过10万条记录) -- 执行查询 select a.key, a.value, b.value from big_table a join small_table b on a.key = b.key; ``` ### 总结 - 小很小:直接用Map Join。 - 小(超过Map Join阈值但可接受广播):强制Map Join(引用[3])。 - 小且无法广播:使用打散小扩容。 - 中有倾斜key:使用Skew Join。 请根据实际数据情况选择合适的方法。 §§相关问题§§ 1. Hive中Map Join的原理是什么?适用于什么场景? 2. 如何判断Hive查询是否发生了数据倾斜? 3. 除了与小Join,还有哪些情况会导致Hive数据倾斜? 4. 如何设置Hive的Map Join小阈值?</think>### Hive 数据倾斜问题解决方案(与小 Join) 针对与小 Join 时的数据倾斜问题,以下是经过验证的解决方案: #### 1. **开启 MapJoin(最优方案)** 当小符合尺寸要求时,**MapJoin 是最有效的解决方案**,它完全避免 Reduce 阶段从而规避数据倾斜: ```sql -- 启用自动 MapJoin 转换 SET hive.auto.convert.join = true; -- 调整小尺寸阈值(默认 25MB) SET hive.mapjoin.smalltable.filesize = 100000000; -- 提升至 100MB -- 执行 Join(自动触发 MapJoin) SELECT /*+ MAPJOIN(small_table) */ big_table.id, small_table.info FROM big_table JOIN small_table ON big_table.key = small_table.key; ``` **原理**:将小加载到所有 Map 任务内存中,直接完成 Join 操作[^1][^3]。 #### 2. **手动 MapJoin(强制广播)** 当小略超阈值但内存允许时,强制广播: ```sql SELECT /*+ MAPJOIN(small_table) */ ... FROM big_table JOIN small_table ON ...; ``` 需确保小不超过 **100MB**(超出可能导致 OOM)[^3]。 #### 3. **打散 + 小扩容** 当小无法广播时(如 100MB-1GB),采用分布式扩容方案: ```sql -- 步骤1:添加随机前缀(0-99) SELECT key, CONCAT(CAST(FLOOR(RAND() * 100) AS STRING), '_', key) AS rand_key, value FROM big_table; -- 步骤2:小扩容 100 倍 SELECT CONCAT(num, '_', key) AS rand_key, value FROM small_table LATERAL VIEW EXPLODE(SPLIT(SPACE(99), ' ')) nums AS num; -- 生成 100 行 -- 步骤3:关联处理后的 SELECT SPLIT(t1.rand_key, '_')[1] AS original_key, t1.value, t2.value FROM modified_big_table t1 JOIN expanded_small_table t2 ON t1.rand_key = t2.rand_key; ``` **优势**:将倾斜 Key 分散到多个 Reduce 处理[^1][^2]。 #### 4. **倾斜 Key 单独处理** 针对已知的倾斜 Key(如 NULL 或特定值): ```sql -- 处理倾斜 Key SELECT * FROM big_table WHERE key IS NULL -- 倾斜部分 UNION ALL SELECT /*+ MAPJOIN(small_table) */ -- 正常部分 big_table.* FROM big_table JOIN small_table ON big_table.key = small_table.key WHERE big_table.key IS NOT NULL; ``` #### 5. **启用 Skew Join 优化** 通用倾斜处理(需 Hive 0.10+): ```sql SET hive.optimize.skewjoin = true; SET hive.skewjoin.key = 100000; -- Key 超过 10W 条视为倾斜 SELECT ... FROM big_table JOIN small_table ON ...; ``` 倾斜 Key 会自动拆分到多个 Reduce[^2]。 --- ### **方案选择建议** | 场景 | 推荐方案 | 注意事项 | |------|----------|----------| | 小 < 100MB | MapJoin | 最优解,避免 Reduce | | 小 100MB~1GB | 打散 + 小扩容 | 需计算扩容倍数 | | 存在热点 Key | 倾斜 Key 单独处理 | 需提前识别倾斜 Key | | 未知倾斜模式 | Skew Join | 通用但效率低于 MapJoin | > **关键参数调优**: > - `hive.mapjoin.smalltable.filesize`:根据集群内存调整 > - `hive.exec.reducers.bytes.per.reducer`:控制 Reduce 数据量(默认 1GB) > - `hive.optimize.skewjoin.key`:根据数据分布调整阈值 通过组合这些策略,可有效解决 90% 以上的-小 Join 倾斜问题[^1][^2][^3]。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值