
目录
2.8.2.1 hive.groupby.mapaggr.checkinterval
2.8.2.2 hive.map.aggr.hash.min.reduction
2.8.2.3 hive.map.aggr.hash.percentmemory
2.8.2.4 hive.groupby.skewindata
2.9.1.2 hive.merge.mapredfiles
2.9.1.3 hive.merge.size.per.task
2.10.1.1 mapred.max.split.size
2.10.1.2 mapred.min.split.size.per.node
2.10.1.3 mapred.min.split.size.per.rack
2.10.2.2 hive.exec.reducers.bytes.per.reducer
2.10.2.3 hive.exec.reducers.max
Hive的存储层依托于HDFS,Hive的计算层依托于MapReduce,一般Hive的执行效率主要取决于SQL语句的执行效率,因此,Hive的优化的核心思想是MapReduce的优化。
Hive的SQL语句在执行之前需要将SQL语句转换成MapReduce任务,因此需要了解具体的转换过程,可以在SQL语句中输入如下命令查看具体的执行计划。
语法为:
explain [extended] query
例如:
explain extended select * from psn8;

Hive的某些SQL语句需要转换成MapReduce的操作,某些SQL语句就不需要转换成MapReduce操作,但是需要注意,理论上来说,所有的SQL语句都需要转换成MapReduce操作,只不过Hive在转换SQL语句的过程中会做部分优化,使某些简单的操作不再需要转换成MapReduce。
我们可以通过使用sql语句设置Hive的抓取策略:
set hive.fetch.task.conversion=none/more;
或者可以在hive-site.xml配置文件中设置:

- <property>
- <name>hive.fetch.task.conversion</name>
- <value>more</value>
- </property>
该配置候选值有三个,分别为:
1)none:disable hive.fetch.task.conversion
2)minimal : SELECT STAR, FILTER on partition columns, LIMIT only
3)more : SELECT, FILTER, LIMIT only (support TABLESAMPLE and virtual columns)
我们来看下对比效果:
开启抓取策略前:
关闭一下抓取策略:
set hive.fetch.task.conversion=none;
执行查询语句:
select * from psn8;
查看执行效果:

开启抓取策略后:
开启抓取策略:
set hive.fetch.task.conversion=more;
执行查询语句:
select * from psn8;
执行效果:

类似于MapReduce的操作,Hive的运行也分为本地模式和集群模式,在开发阶段可以选择使用本地执行,提高SQL语句的执行效率,验证SQL语句是否正确。
假设你正在运行一些复杂的 Hive 查询,我们都知道这会在后台触发 MapReduce 作业并为你提供输出。如果 Hive 中的数据比较大,这种方法比较有效,但如果Hive 表中的数据比较少,这样会有一些问题。出现此问题的主要原因是 MapReduce 作业被触发,它是在服务器/集群上触发,因此每次运行查询时,它都会上传到服务器并在那里启动 MapReduce,然后输出。因此,为查询触发执行任务的时间消耗可能会比实际作业的执行时间要多的多。
需要满足如下三个配置条件,才能在本地模式下运行 Hive 查询:

| 参数 | 默认值 | 描述 |
| hive.exec.mode.local.auto | false | 让Hive确定是否自动启动本地模式运行 |
| hive.exec.mode.local.auto.inputbytes.max | 134217728(128MB) | 当第一个参数为true时,输入字节小于此值时才能启动本地模式 |
| hive.exec.mode.local.auto.input.files.max | 4 | 当一个参数为true时,任务个数小于此值时才能启动本地模式 |
通过命令设置配置:
- set hive.exec.mode.local.auto=false;
-
- set hive.exec.mode.local.auto.inputbytes.max=134217728;
-
- set hive.exec.mode.local.auto.input.files.max=5;
执行统计命令:
select count(*) from psn8;
查看效果:
我们发现yarn上提交了一个计算任务:

任务执行时间:

我们发现,统计完成花费了54.211秒
通过命令设置配置:
set hive.exec.mode.local.auto=true;
执行统计命令:
select count(*) from psn8;
查看效果:

可以看到,设置成本地模式后,耗时1.513秒
在SQL语句足够复杂的情况下,可能在一个SQL语句中包含多个子查询语句,且多个子查询语句之间没有任何依赖关系,此时,可以Hive运行的并行度。
设置命令:
set hive.exec.parallel=true;
注意:Hive的并行度并不是无限增加的,在一次SQL计算中,可以通过以下参数来设置并行的job的个数。
设置一次SQL计算允许并行执行的job个数的最大值
设置命令:
set hive.exec.parallel.thread.number
Hive中为了提高SQL语句的执行效率,可以设置严格模式,充分利用Hive的某些特点。
实现命令:
查看当前模式:
set hive.mapred.mode;
严格模式:
set hive.mapred.mode=strict;
非严格模式:
set hive.mapred.mode=nostrict;
如果在一个分区表执行hive,除非where语句中包含分区字段过滤条件来显示数据范围,否则不允许执行。换句话说, 就是用户不允许扫描所有的分区。进行这个限制的原因是,通常分区表都拥有非常大的数据集,而且数据增加迅速。
例子:比如我这里有一张分区表,设置了分区字段age,具体如下:
创建表语句:
- create external table psn11
- (
- id int,
- name string,
- likes array<string>,
- address map<string,string>
- )
- partitioned by(age int)
- row format delimited
- fields terminated by ','
- collection items terminated by '-'
- map keys terminated by ':'
- location '/ning';
在hdfs创建目录并上传文件:
- hdfs dfs -mkdir /ning
- hdfs dfs -mkdir /ning/age=10
- hdfs dfs -mkdir /ning/age=20
- hdfs dfs -put /root/data/data /ning/age=10
- hdfs dfs -put /root/data/data /ning/age=20


设置严格模式前:
执行命令:
select * from psn11;
查询结果:

设置严格模式后:
执行命令:
select * from psn11;
查询结果:

直接报错了,因为严格模式下,不允许扫描所有的分区。
修改后命令:
select * from psn11where age=10;
查询结果:

对于使用了order by的查询,要求必须有limit语句。因为order by为了执行排序过程会将所有的结果分发到同一个reducer中进行处理,强烈要求用户增加这个limit语句可以防止reducer额外执行很长一段时间。
我们还拿上一小节创建的psn11这张分区表做例子。
在开启严格模式前
执行命令:
select * from psn11 where age=10 order by id;
查询效果:

发现查询出了数据。
开启严格模式后:
执行命令:
select * from psn11 where age=10 order by id;
查询效果:

开启严格模式后,同样的sql 语句,查询报错了,提示我们需要限制查询数据条数。
修改后指令:
select * from psn11 where age=10 order by id limit 10;
查询效果:

我们加上limit限制查询条数后,发现执行成功了。
对关系型数据库非常了解的用户可能期望在执行join查询的时候不使用on语句而是使用where语句,这样关系数据库的执行优化器就可以高效的将where语句转换成那个on语句。but,hive不会执行这种优化,所以如果表足够大,那么这个查询就会出现不可控的情况。
我们创建两张表,一张人员表,一张人员课程表
建表语句:
- create external table psn14
- (
- id int,
- p_id int,
- create_time string,
- name string,
- number int,
- learn_time string,
- )
- row format delimited
- fields terminated by ','
- location '/data/couses';
- create external table psn15
- (
- id int,
- name string,
- likes array<string>,
- address map<string,string>
- )
- row format delimited
- fields terminated by ','
- collection items terminated by '-'
- map keys terminated by ':'
- location '/ning/data';
创建目录:
- hdfs dfs -mkdir /data/couses
-
- hdfs dfs -mkdir /ning/data
上传文件:
- hdfs dfs -put /opt/hadoop/data/courses1.txt /data/couses
-
- hdfs dfs -put /opt/hadoop/data/data1.txt /ning/data
执行笛卡尔乘积
未设置严格模式前:
执行命令:
select * from psn15 JOIN psn14 where1=1;
查询结果:

我们发现,查询成功,并且成功关联了数据。
设置严格模式后:
执行命令:
select * from psn15 JOIN psn14 where1=1;
查询结果:

开启严格模式后,同样的查询语句,查询报错了。
改进查询语句:
select * from psn15 JOIN psn14 ON (psn15.id =psn14.p_id);
查询结果:

在编写SQL语句的过程中,很多情况下需要对数据进行排序操作,Hive中支持多种排序操作适合不同的应用场景。
Order By - 对于查询结果做全排序,只允许有一个reduce处理(当数据量较大时,应慎用。严格模式下,必须结合limit来使用)
我们来验证下:
执行如下语句:
select * from psn14 order by id limit 10;
执行结果:


我们可以看到,实际上我们设置了三个reduce,但是order by只是使用了一个reduce计算。
对于单个reduce的数据进行排序
我们执行如下语句:
select * from psn14 sort by id;

我们可以看到,返回的数据在reduce job中分别进行排序。
有些场景我们需要控制某些特定行应该到同一reducer,做一些聚集操作。
distribute by 类似 MR 中 partition(自定义分区),进行分区,经常结合 sort by 使用。
分区逻辑:根据distribute by 后的字段hash码与reduce 的个数进行模数后,决定分区路由。
我们执行如下语句:
select * from psn14 distribute by p_id sort by number;

相当于 Sort By + Distribute By(Cluster By不能通过asc、desc的方式指定排序规则;可通过 distribute by column sort by column asc|desc 的方式)。
select * from psn14 cluster by number;
执行效果:

select * from psn14 distribute by number sort by number;
执行效果:

可以看出,两条语句查询的结果是一致的。cluster by 等价于distribute by 和 sort by 字段的升序排序。
小表进行mapjoin,如果在join的表中,有一张表数据量较小,可以存于内存中,这样该表在和其他表join时可以直接在map端进行,省掉reduce过程,效率高。设置方式主要分两种:自动JOIN和手动JOIN。
设置开启自动JOIN:
set hive.auto.convert.join=true;
提示:该参数为true时,Hive自动对左边的表统计量,如果是小表就加入内存,即对小表使用Map join。
设置Map JOIN的表的大小(默认为25M):
set hive.mapjoin.smalltable.filesize
提示:该参数是大表小表判断的阈值,如果表的大小小于该值则会被加载到内存中运行。
设置是否忽略mapjoin标记:
set hive.ignore.mapjoin.hint=true;
提示:默认值:true;是否忽略mapjoin hint 即mapjoin标记
例子:
select p15.*, p14.* from psn15 p15 JOIN psn14 p14 ON (p15.id =p14.p_id);

手动Map join在map端完成join操作。
- SELECT /*+ MAPJOIN(smallTable) */ smallTable.key, bigTable.value
- FROM smallTable JOIN bigTable ON smallTable.key = bigTable.key;
通过SQL方式,在SQL语句中添加MapJoin标记(mapjoin hint)。
例如:
select /*+ MAPJOIN(p15) */ p15.*,p14.* from psn15 p15 JOIN psn14 p14 ON (p15.id =p14.p_id);

有时join超时是因为某些key对应的数据太多,而相同key对应的数据都会发送到相同的reducer上,从而导致内存不够。此时我们应该仔细分析这些异常的key,很多情况下,这些key对应的数据是异常数据,我们需要在SQL语句中进行过滤。
有时虽然某个key为空对应的数据很多,但是相应的数据不是异常数据,必须要包含在join的结果中,此时我们可以表a中key为空的字段赋一个随机的值,使得数据随机均匀地分不到不同的reducer上。
Hive的某些SQL操作可以实现map端的聚合,类似于MR的combine操作。
通过设置以下参数开启在Map端的聚合:
set hive.map.aggr=true;
map端group by执行聚合时处理的多少行数据(默认:100000,可根据实际情况修改)
设置命令:
set hive.groupby.mapaggr.checkinterval=100000;
进行聚合的最小比例(预先对100000条数据做聚合,若聚合之后的数据量/100000的值大于该配置0.5,则不会聚合)
设置命令:
set hive.map.aggr.hash.min.reduction=0.5;
map端聚合使用的内存的最大值(默认值0.5,可根据实际情况修改)
设置命令:
set hive.map.aggr.hash.percentmemory=0.5;
是否对GroupBy产生的数据倾斜做优化,默认为false
设置命令:
set hive.groupby.skewindata=false;
Hive在操作的时候,如果文件数目小,容易在文件存储端造成压力,给hdfs造成压力,影响效率。
是否合并map输出文件:
set hive.merge.mapfiles=true
是否合并reduce输出文件:
set hive.merge.mapredfiles=true;
合并文件的大小:
set hive.merge.size.per.task=256*1000*1000
一个split的最大值,即每个map处理文件的最大值
设置命令:
set mapred.max.split.size
一个节点上split的最小值
设置命令:
set mapred.min.split.size.per.node
一个机架上split的最小值
设置命令:
set mapred.min.split.size.per.rack
强制指定reduce任务的数量
设置命令:
set mapred.reduce.tasks
每个reduce任务处理的数据量
设置命令:
set hive.exec.reducers.bytes.per.reducer
每个任务最大的reduce数
设置命令:
set hive.exec.reducers.max
Hadoop的默认配置通常是使用派生JVM来执行map和Reduce任务的。这时JVM的启动过程可能会造成相当大的开销,尤其是执行的job包含有成百上千task任务的情况。JVM重用可以使得JVM实例在同一个job中重新使用N次。
执行命令:
set mapred.job.reuse.jvm.num.tasks=n;(n为task插槽个数)
1)小文件个数过多
2)task个数过多
开启JVM重用将一直占用使用到的task插槽,以便进行重用,直到任务完成后才能释放。如果某个“不平衡的”job中有某几个reduce task执行的时间要比其他Reduce task消耗的时间多的多的话,那么保留的插槽就会一直空闲着却无法被其他的job使用,直到所有的task都结束了才会释放。
好了,今天Hive调优的相关内容就分享到这里,如果帮助到大家,欢迎大家点赞+关注+收藏,有疑问也欢迎大家评论留言!