要点:

优化时,把 hive sql 当做 map reduce 程序来读,会有意想不到的惊喜。
理解 hadoop 的核心能力,是 hive 优化的根本。
长期观察 hadoop 处理数据的过程,有几个显著的特征:

1.不怕数据多,就怕数据倾斜。
2.对 jobs 数比较多的作业运行效率相对比较低,比如即使有几百行的表,如果多次关联
多次汇总,产生十几个 jobs,没半小时是跑不完的。map reduce 作业初始化的时间是比
较长的。
3.对 sum,count 来说,不存在数据倾斜问题。
4.对 count(distinct ),效率较低,数据量一多,准出问题,如果是多 count(distinct )效率更
低。

优化可以从几个方面着手:

1. 好的模型设计事半功倍。
2. 解决数据倾斜问题。
3. 减少 job 数。
4. 设置合理的 map reduce 的 task 数,能有效提升性能。(比如,10w+级别的计算,用
160 个 reduce,那是相当的浪费,1 个足够)。
5. 自己动手写 sql 解决数据倾斜问题是个不错的选择。set hive.groupby.skewindata=true;
这是通用的算法优化,但算法优化总是漠视业务,习惯性提供通用的解决方法。 Etl 开发
人员更了解业务,更了解数据,所以通过业务逻辑解决倾斜的方法往往更精确,更有效。
6. 对 count(distinct)采取漠视的方法,尤其数据大的时候很容易产生倾斜问题,不抱侥幸
心理。自己动手,丰衣足食。
7. 对小文件进行合并,是行至有效的提高调度效率的方法,假如我们的作业设置合理的文
件数,对云梯的整体调度效率也会产生积极的影响。
8. 优化时把握整体,单个作业最优不如整体最优。

优化案例:

问题 1:如日志中,常会有信息丢失的问题,比如全网日志中的 user_id,如果取其中的
user_id 和 bmw_users 关联,就会碰到数据倾斜的问题。

解决方法 1. User_id 为空的不参与关联,例如:
Select *
From log a
Join bmw_users b
On a.user_id is not null
And a.user_id = b.user_id
Union all
Select *
from log a
where a.user_id is null.

解决方法 2 :
Select *
from log a
left outer join bmw_users b
on case when a.user_id is null then concat(‘dp_hive’,rand() ) else a.user_id end = b.user_id;

总结:2 比 1 效率更好,不但 io 少了,而且作业数也少了。1 方法 log 读取两次,jobs 是
2。2 方法 job 数是 1 。这个优化适合无效 id(比如-99,’’,null 等)产生的倾斜问题。把空值
的 key 变成一个字符串加上随机数,就能把倾斜的数据分到不同的 reduce 上 ,解决数据倾
斜问题。因为空值不参与关联,即使分到不同的 reduce 上,也不影响最终的结果。附上
hadoop 通用关联的实现方法(关联通过二次排序实现的,关联的列为 parition key,关联的
列 c1 和表的 tag 组成排序的 group key,根据 parition key 分配 reduce。同一 reduce 内根
据 group key 排序)。

问题 2:不同数据类型 id 的关联会产生数据倾斜问题。

一张表 s8 的日志,每个商品一条记录,要和商品表关联。但关联却碰到倾斜的问题。s8
的日志中有字符串商品 id,也有数字的商品 id,类型是 string 的,但商品中的数字 id 是 bigint
的。猜测问题的原因是把 s8 的商品 id 转成数字 id 做 hash 来分配 reduce,所以字符串 id
的 s8 日志,都到一个 reduce 上了,解决的方法验证了这个猜测。

方法:把数字类型转换成字符串类型
Select * from s8_log a
Left outer join r_auction_auctions b
On a.auction_id = cast(b.auction_id as string);

问题 3:利用 hive 对 UNION ALL 的优化的特性
hive 对 union all 优化只局限于非嵌套查询。

比如以下的例子:
select * from
(select * from t1
Group by c1,c2,c3
Union all
Select * from t2
Group by c1,c2,c3) t3
Group by c1,c2,c3;
从业务逻辑上说,子查询内的 group by 怎么都看显得多余(功能上的多余,除非有
count(distinct)),如果不是因为 hive bug 或者性能上的考量(曾经出现如果不子查询
group by ,数据得不到正确的结果的 hive bug)。所以这个 hive 按经验转换成
select * from
(select * from t1
Union all
Select * from t2
) t3
Group by c1,c2,c3;
经过测试,并未出现 union all 的 hive bug,数据是一致的。mr 的作业数有 3 减少到 1。
t1 相当于一个目录,t2 相当于一个目录,那么对 map reduce 程序来说,t1,t2 可以做为
map reduce 作业的 mutli inputs。那么,这可以通过一个 map reduce 来解决这个问题。
Hadoop 的计算框架,不怕数据多,就怕作业数多。
但如果换成是其他计算平台如 oracle,那就不一定了,因为把大的输入拆成两个输入,分
别排序汇总后 merge(假如两个子排序是并行的话),是有可能性能更优的(比如希尔排序
比冒泡排序的性能更优)。

问题 4:比如推广效果表要和商品表关联,效果表中的 auction id 列既有商品 id,也有数
字 id,和商品表关联得到商品的信息。那么以下的 hive sql 性能会比较好

Select * from effect a
Join (select auction_id as auction_id from auctions
Union all
Select auction_string_id as auction_id from auctions
) b
On a.auction_id = b.auction_id。
比分别过滤数字 id,字符串 id 然后分别和商品表关联性能要好。
这样写的好处,1 个 MR 作业,商品表只读取一次,推广效果表只读取一次。把这个 sql 换成
MR 代码的话,map 的时候,把 a 表的记录打上标签 a,商品表记录每读取一条,打上标签
b,变成两个<key ,value>对,<b,数字 id>,<b,字符串 id>。所以商品表的 hdfs 读只会是
一次。

问题 5:先 join 生成临时表,在 union all 还是写嵌套查询,这是个问题。比如以下例
子:

Select *
From (select *
From t1
Uion all
select *
From t4Union all
Select *
From t2
Join t3
On t2.id = t3.id
) x
Group by c1,c2;
这个会有 4 个 jobs。假如先 join 生成临时表的话 t5,然后 union all,会变成 2 个 jobs。
Insert overwrite table t5
Select *
From t2
Join t3
On t2.id = t3.id
;
Select * from (t1 union all t4 union all t5) ;
hive 在 union all 优化上可以做得更智能(把子查询当做临时表),这样可以减少开发人
员的负担。出现这个问题的原因应该是 union all 目前的优化只局限于非嵌套查询。如果
写 MR 程序这一点也不是问题,就是 multi inputs。