# Hive map阶段优化之一次详细的优化分析过程

2017/03/27 00:21

#背景 同事写了这样一段HQL(涉及公司数据，表名由假名替换，语句与真实场景略有不同，但不影响分析)：

``````CREATE TABLE tmp AS
SELECT
t1.exk,
t1.exv,
M.makename AS m_makename,
S.makename AS s_makename,
FROM
(SELECT
exk,
exv
FROM xx.xxx_log
WHERE etl_dt = '2017-01-12'
AND exk IN ('xxID', 'yyID') ) t1
LEFT JOIN xx.xxx_model_info M ON (M.modelid=t1.exv AND t1.exk='xxID')
LEFT JOIN xx.xxx_style_info S ON (S.styleid=t1.exv AND t1.exk='yyID')
``````

``````STAGE DEPENDENCIES:
Stage-5 is a root stage
Stage-4 depends on stages: Stage-5
Stage-0 depends on stages: Stage-4

STAGE PLANS:
Stage: Stage-5
Map Reduce Local Work
Alias -> Map Local Tables:
m
Fetch Operator
limit: -1
s
Fetch Operator
limit: -1
Alias -> Map Local Operator Tree:
m
TableScan
alias: m
Statistics: Num rows: 3118 Data size: 71714 Basic stats: COMPLETE Column stats: NONE
HashTable Sink Operator
filter predicates:
0 {(_col0 = 'xxID')} {(_col0 = 'yyID')}
1
2
keys:
0 UDFToDouble(_col1) (type: double)
1 UDFToDouble(modelid) (type: double)
2 UDFToDouble(styleid) (type: double)
s
TableScan
alias: s
Statistics: Num rows: 44482 Data size: 1023086 Basic stats: COMPLETE Column stats: NONE
HashTable Sink Operator
filter predicates:
0 {(_col0 = 'xxID')} {(_col0 = 'yyID')}
1
2
keys:
0 UDFToDouble(_col1) (type: double)
1 UDFToDouble(modelid) (type: double)
2 UDFToDouble(styleid) (type: double)

Stage: Stage-4
Map Reduce
Map Operator Tree:
TableScan
alias: xx.xxx_log
Statistics: Num rows: 136199308 Data size: 3268783392 Basic stats: COMPLETE Column stats: NONE
Filter Operator
predicate: ((exk) IN ('xxID', 'yyID')) (type: boolean)
Statistics: Num rows: 22699884 Data size: 544797216 Basic stats: COMPLETE Column stats: NONE
Select Operator
expressions: exk (type: string), exv (type: string)
outputColumnNames: _col0, _col1
Statistics: Num rows: 22699884 Data size: 544797216 Basic stats: COMPLETE Column stats: NONE
Map Join Operator
condition map:
Left Outer Join0 to 1
Left Outer Join0 to 2
filter predicates:
0 {(_col0 = 'SerialID')} {(_col0 = 'CarID')}
1
2
keys:
0 UDFToDouble(_col1) (type: double)
1 UDFToDouble(modelid) (type: double)
2 UDFToDouble(styleid) (type: double)
outputColumnNames: _col0, _col1, _col13, _col32
Statistics: Num rows: 49939745 Data size: 1198553901 Basic stats: COMPLETE Column stats: NONE
Select Operator
expressions: _col0 (type: string), _col1 (type: string), _col13 (type: string), _col32 (type: string)
outputColumnNames: _col0, _col1, _col2, _col3
Statistics: Num rows: 49939745 Data size: 1198553901 Basic stats: COMPLETE Column stats: NONE
File Output Operator
compressed: false
Statistics: Num rows: 49939745 Data size: 1198553901 Basic stats: COMPLETE Column stats: NONE
table:
Local Work:
Map Reduce Local Work

Stage: Stage-0
Fetch Operator
limit: -1
Processor Tree:
ListSink
``````

##第二步，查建表语句，看表的压缩格式是不是不支持分割导致部分map任务处理时间过长 Hive支持多种压缩格式，有的压缩格式支持split，而有的并不支持，比如LZO。当不支持split的时候，数据块有多大，Hive的map任务就得处理多大，而Hive表的分区数据有可能存在不均衡的现象，就会导致有的map快，有的map慢。当遇到LZO格式的时候，最好的方式是建立索引，可以加快处理速度。

`show create table`从建表语句里看，并没有使用LZO的表。而在hdfs上直接查看三张表的文件大小，最大的那张表加上条件后还有24个分区，每个分区的大小不一样。但是由于任务有40个map，由此可知有些分区的数据是拆成了几个map任务的，所以再一次证明是可切分的，排除不可切分导致的map任务问题。

##第三步，分析任务运行状况 找到运行完的任务，查看运行界面图可以看到，map任务的时间长短不一，最短的1分钟之内，最长的达半个多小时。

• `top` 命令可以辅助我们查看CPU的状况，结果发现CPU平均负载不过50%
• `iostat -x 5` 命令可以辅助我们查看磁盘IO情况，我们发现请求数比较高但是平均等待队列并不高，磁盘读写都跟得上，所以磁盘也不是问题
• `sar -n DEV 5` 命令可以辅助我们查看网络IO的情况，服务器至少是千兆网卡，支持至少1Gb/s的速度，而从输出来看，网络远远不是问题

##第四步，再触SQL，分段分析 上面的分析已经确认跟机器无关，与数据不可分割也无关，而执行计划上看也没什么问题。那么只好一段一段的来看SQL了。

1、两张小表是要分发到各节点的，所以不考虑，我们按条件读一次大表的数据，统计下行数

``````SELECT COUNT(1)
FROM xx.xxx_log
WHERE etl_dt = '2017-01-12'
AND exk IN ('xxID', 'yyID')
``````

2、考虑只join一张表来看 先选表`xx.xxx_model_info`

``````SELECT COUNT(1)
from (
SELECT
t1.exk,
t1.exv,
M.makename AS m_makename
FROM
(SELECT
exk,
exv
FROM xx.xxx_log
WHERE etl_dt='2017-01-12'
AND exk IN ('xxID', 'yyID') ) t1
LEFT JOIN xx.xxx_model_info M ON (M.modelid=t1.exv AND t1.exk='xxID')) tmp
``````

``````SELECT COUNT(1)
from (
SELECT
t1.exk,
t1.exv,
S.makename AS s_makename
FROM
(SELECT
exk,
exv
FROM xx.xxx_log
WHERE etl_dt='2017-01-12'
AND exk IN ('xxID', 'yyID') ) t1
LEFT JOIN xx.xxx_style_info S ON (S.styleid=t1.exv AND t1.exk='yyID') ) tmp
``````

##第五步，仍不放弃执行计划 当看到上面问题的时候，一头雾水，所以着重再看执行计划是一个不错的方案。很容易想到，同是两个数据量相差不大的小表，mapjoin的运行速度为什么会不一样？是字段类型导致join连接出问题？

`double`类型是`string`类型和`int`类型的公共类型，所以它们都会往公共类型上转！

``````SELECT COUNT(1)
from (
SELECT
t1.exk,
t1.exv,
S.makename AS s_makename
FROM
(SELECT
exk,
exv
FROM xx.xxx_log
WHERE etl_dt='2017-01-12'
AND exk IN ('xxID', 'yyID') ) t1
LEFT JOIN xx.xx_style_info S ON (cast(S.styleid as string)=t1.exv AND t1.exk='yyID') ) tmp
``````

##第六步，解开谜团 到这里，我们这个Hive任务的问题已经找到，那就是join两边key的数据类型不对，导致两边的数据类型都要向上做提升才能关联。

``````return (int)Double.doubleToLongBits(value);
``````

long转为int会产生溢出，因此不同的value很可能得到相同的hashcode，hashcode碰撞非常明显。

``````long dblBits = Double.doubleToLongBits(value);
return (int) (dblBits ^ (dblBits >>> 32));
``````

3
5 收藏

0 评论
5 收藏
3