这里记录一下hive任务调优的三(n)板斧.
map join
对于存在join的sql,首先最简单的就是开启map join:
1 | set hive.auto.convert.join = true ; -- 开启自动转化成mapjoin |
sql中足够小的表应该放在join操作左边. 由于小表数据会被广播到各个节点,消除了shuffle运算,提高了运算效率.
前提当然是存在足够小的表. 实际业务中一般是各种维度表.
排序消除
注: 是否加速取决于数据集.
排序属于非常耗时的操作(O(nlogn)
),所以对于order by,sort by语句,可以从语义上寻找突破口. 例如对于每天最后一次的用户行为,原来的可能是这样写的:
1 | SELECT * |
可以改为先求最大时间戳,再进行join
(map join
):
1 | select * from |
这样更改后虽然消除了排序操作,但是引入了shuffle操作(join)(并且对于hive要laod两遍数据),因此是否加速取决于具体的数据集. 对于任务卡死(或者很慢)在reduce阶段的hive任务,可以尝试进行排序消除.
实际经验来看,如果数据量大到导致外排,需要消除order by
.
distinct消除(两阶段group by)
回字有4种写法,而distinct一般有2种.
1. 多列或1列去重
1 | select distinct a,b,udf(c1) as c2 from xxx |
由于hive是通过group by
实现distinct,上述sql其实等效于:
1 | select a,b,udf(c1) from xxx group by a,b,udf(c1) |
可以通过explain查看两者的执行计划是完全一致的.
如果能确定udf是单射变换,也就是c1到c2是一对一,而没有多对一,可以等效改写为:
1 | select a,b,udf(c1) from xxx group by a,b,c1 |
总之,对于这个场景下的distinct使用,如果没有udf,可以不进行消除.
2. 聚合函数中使用(如uv计算)
1 | select dt,count(distinct userid) as uv |
这种聚合函数中使用distinct属于比较常见的业务查询需求,hive执行时会把所有数据灌到一个reducer中,毫无并行度.
可以使用两阶段group by进行优化,写法:
1 | select dt,count(1) |
这样去重操作在第一个阶段分担到了多个reducer上,速度提升很多.
实际优化的时候,主要有三种情况阻碍,无法直接改写:
1. 同一列不同条件的count distinct
1 | select dt |
可以通过增加标记列转化:
1 | select dt |
2. 多维聚合(group by with cube)
可以通过一行变多行,手动维护grouping sets的组合:
1 | lateral view explode(array('全部',platform)) tt1 as platform_t |
不同列聚合.
例如:
1 | count(distinct userid) |
这种如果确实出现了reduce卡死,可以进行分拆成两个查询分别计算(load两遍数据),最后join到一起. 代码会比较长.