按照 Kimball 维度模型设计出来的数据表,在开发 ETL 的过程中,不可避免地会使用到多表 Join 关联。在 Join 关联两个表时,每个表 Map 端输出的 key 会带上表别名标识,经过 shuffle 分发到 Reduce 端就有可能会出现数据分布不均匀,从而造成数据倾斜,影响业务的数据质量。然而,相对 count(distinct) 来说,Join 产生数据倾斜的场景会多一些,本文将会介绍几种 Join 典型场景并给出优化实践。

0x00 说在前面

假设有两张表:一张表是用户打开APP列表 device_open_app(device,appid);另一张表是作弊用户表 device_cheat(device)。

1
2
3
4
5
select a.device, a.appid, if(b.device is null,0,1) as is_cheat
from device_open_app a
left join device_cheat b
on a.device = b.device
;

0x01 通用 Join 优化方式

  • 做好列裁剪和filter操作,以达到两表做join时数据量相对较小;
  • 关联时小表在前大表在后;
  • 确保关联条件的字段类型是一致的;
  • 选择join key分布最均匀的表作为驱动表;
  • 设置在map端聚合,set hive.map.aggr=true(默认开启);
  • 可选set hive.optimize.skewjoin=false(默认关闭);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
set hive.map.aggr=true;
set hive.optimize.skewjoin=true;

select a.device, b.appid
from (
select device
from device_cheat
where device is not null
) a
inner join (
select device, appid
from device_open_app
where device is not null
) b
on cast(a.device as string) = b.device
;

0x02 小表与大表 Join 优化方式

这是一种典型的场景,Hive 使用 MapJoin 方式对关联进行优化。

MapJoin 原理

MapJoin 简单说就是在 Map 阶段将小表读入分布式缓存,然后顺序扫描大表完成 Join。

1
2
3
4
5
6
7
8
9
---在0.7.0版本之后
set hive.auto.convert.join=true;

select /*+MAPJOIN(b)*/ a.device, a.appid
,if(b.device is null,0,1) as is_cheat
from device_open_app a
left join device_cheat b
on a.device = b.device
;

0x03 大表与大表 Join 优化方式

这种关联的场景比较多,最常见的场景是业务数据本身的特性,比如关联字段有大量的空值或特殊值;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
set hive.auto.convert.join=true;

select a.device, a.appid, if(b.device is null,0,1) as is_cheat
from (
select device, appid
from device_open_app
where device is not null
) a
left join (
select device
from device_cheat
where device is not null
) b
on if(a.device='0', concat('hive',rand()), a.device) = cast(b.device as string)
;

参考文献

MapReduce执行过程详解
HiveQL的Select语句
Hive – JOIN实现过程
Hive数据倾斜(大表join大表)