目录
SQL语句优化
count优化
count distinct 用sum...group by替换/count ... group by
将两个join的表提前filter
join 过滤掉ID为空的数据(与上面类似)
join操作时,小表关联大表
配置优化
hive-site.xml配置
动态分区:
map阶段优化
减少map数
增大map数量
reduce阶段优化
map和reduce优化。
MapReduce输出结果(包括中间结果)合并小文件
小文件是如何产生的
小文件问题的影响
小文件问题的解决方案
MAP JOIN
列式存储格式减少读入文件数量
压缩格式减少读入文件数量:
引擎的选择
向量化查询执行
模式选择
JVM重用
推测执行
count由于只有一个reduce来统计。因此如果不优化会非常的慢。那么怎么优化那?
既然整个只产生一个reduce,那么我们就把他们分别统计,然后在统计。那么如何实现分别统计,通过group by分组。那么分组不能按照被统计的列分组。可以制造一个flag,来进行分组。
首先我们第一层:
select cast(rand() * 10 as bigint) flag,A from tablename group by A
我们记为表t1
1层使用随机数作为分组依据,同时使用group by A保证去重。
第二层,每个分组进行统计
select count(*) tc, flag from t1 group by flag
上面记为t2
第三层对分组结果求和。
SELECT SUM(tc) FROM t2
整合为一个sql为:
SELECT SUM(tc) FROM (select count(*) tc, flag from (select cast(rand() * 10 as bigint) flag,A from tablename group by A )t1 group by flag )t2;
select count(b.id) from (select id from a group by id) b;
select c.a,sum(1) from (select a, b from t group by a,b) c group by a;
select e.a, e.b, e.c from
(select ... from emp where emp.filter ) e
join
(select ... from dept where emdeptp.filter ) d
on (e.deptno = d.deptno)
select n.* from (select * from nullidtable where id is not null ) n left join ori on n.id = o.id;
如果过滤有问题,可以把空ID设置成一个随机ID
select n.* from nullidtable n full join ori o on
case when n.id is null then concat('hive',rand()) else n.id=o.id;
hive.fetch.task.conversion=more
more:简单查询就不走map/reduce了
minimal:任何简单select都会走map/reduce
启动动态分区功能
set hive.exec.dynamic.partition=true;
采用动态方式加载数据到目标表 加载之前先设置一下下面的参数
set hive.exec.dynamic.partition.mode=nonstrict
动态加载数据
insert into dynamic_partition_table partition(deptno) select ... from emp;
删除分区
ALTER TABLE my_partition_test_table DROP IF EXISTS PARTITION (day='2018-08-08');
mapred.min.split.size: 指的是数据的最小分割单元大小;min的默认值是1B
mapred.max.split.size: 指的是数据的最大分割单元大小;max的默认值是256MB
通过调整max可以起到调整map数的作用,减小max可以增加map数,增大max可以减少map数。
需要提醒的是,直接调整mapred.map.tasks这个参数是没有效果的。
当小文件过多,业务不太复杂,(输入端)合并小文件,到达减少map个数
set mapred.max.split.size=100000000;
set mapred.min.split.size.per.node=100000000;
set mapred.min.split.size.per.rack=100000000;
set hive.input.format=org.apache.hadoop.hive.ql.io.CombineHiveInputFormat;
大概解释一下,
100000000表示100M,
set hive.input.format=org.apache.hadoop.hive.ql.io.CombineHiveInputFormat;这个参数表示执行前进行小文件合并,前面三个参数确定合并文件块的大小,大于文件块大小128m的,按照128m来分隔,小于128m,大于100m的,按照100m来分隔,把那些小于100m的(包括小文件和分隔大文件剩下的),进行合并,最终生成了74个块。
文件都很大,任务逻辑复杂,map执行非常慢的时候,可以考虑增加Map数
假设有这样一个任务:
Select data_desc,
count(1),
count(distinct id),
sum(case when …),
sum(case when ...),
sum(…)
from a group by data_desc
如果表a只有一个文件,大小为120M,但包含几千万的记录,如果用1个map去完成这个任务, 肯定是比较耗时的,这种情况下,我们要考虑将这一个文件合理的拆分成多个, 这样就可以用多个map任务去完成。
set mapred.reduce.tasks=10;
create table a_1 as select * from a distribute by rand(10);
这样会将a表的记录,随机的分散到包含10个文件的a_1表中,再用a_1代替上面sql中的a表,
Reduce的个数对整个作业的运行性能有很大影响。
如果Reduce设置的过大,那么将会产生很多小文件,对NameNode会产生一定的影响,而且整个作业的运行时间未必会减少;
如果Reduce设置的过小,那么单个Reduce处理的数据将会加大,很可能会引起OOM异常。
1、如果设置了mapred.reduce.tasks/mapreduce.job.reduces参数,那么Hive会直接使用它的值作为Reduce的个数;
2、如果没有设置,那么Hive会 根据输入文件的大小估算出Reduce的个数。根据输入文件估算Reduce的个数可能未必很准确,因为Reduce的输入是Map的输出,而Map的输出可能会比输入要小, 所以最准确的数根据Map的输出估算Reduce的个数。
2.1、 不指定reduce个数的情况下,Hive自己如何确定reduce数,基于以下两个设定:
hive.exec.reducers.bytes.per.reducer(每个reduce任务处理的数据量,默认为1000^3=1G)
hive.exec.reducers.max(每个任务最大的reduce数,默认为999)
计算reducer数的公式很简单N=min(参数2,总输入数据量/参数1)
即,如果reduce的输入(map的输出)总大小不超过1G,那么只会有一个reduce任务;
解决方法:
1、调整reduce个数方法一:
调整hive.exec.reducers.bytes.per.reducer参数的值;
set hive.exec.reducers.bytes.per.reducer=500000000; (500M)
select pt,count(1) from popt_tbaccountcopy_mes where pt = ‘2012-07-04’ group by pt; 这次有20个reduce
2、调整reduce个数方法二;
set mapred.reduce.tasks = 15;
select pt,count(1) from popt_tbaccountcopy_mes where pt = ‘2012-07-04’ group by pt;这次有15个reduce
3、reduce个数并不是越多越好;
同map一样,启动和初始化reduce也会消耗时间和资源;
另外,有多少个reduce,就会有多少个输出文件,如果生成了很多个小文件,那么如果这些小文件作为下一个任务的输入,
则也会出现小文件过多的问题;
4、 什么情况下只有一个reduce;
很多时候你会发现任务中不管数据量多大,不管你有没有设置调整reduce个数的参数,任务中一直都只有一个reduce任务; 其实只有一个reduce任务的情况,除了数据量小于hive.exec.reducers.bytes.per.reducer参数值的情况外,还有以下原因:
a)、没有group by的汇总
把 select pt,count(1) from popt_tbaccountcopy_mes where pt = ‘2012-07-04’ group by pt;
写成 select count(1) from popt_tbaccountcopy_mes where pt = ‘2012-07-04’;
b)、用了Order by
c)、有笛卡尔积
在设置reduce个数的时候也需要考虑这两个原则:
使大数据量利用合适的reduce数;
使单个reduce任务处理合适的数据量;
1.动态分区插入数据,产生大量的小文件,从而导致map数量剧增。
2.reduce数量越多,小文件也越多(reduce的个数和输出文件是对应的)。
3.数据源本身就包含大量的小文件。
1、小文件会开很多map,一个map开一个JVM去执行,所以这些任务的初始化,启动,执行会浪费大量的资源,严重影响性能。
2、在HDFS中,每个小文件对象约占150byte,如果小文件过多会占用大量内存。这样NameNode内存容量严重制约了集群的扩展。
从源头上控制小文件数量
1.使用Sequencefile作为表存储格式,不要用textfile,在一定程度上可以减少小文件。
2.减少reduce的数量(可以使用参数进行控制)。
3.少用动态分区,用时记得按distribute by分区。
对于已有的小文件,我们可以通过以下几种方案解决:
1.使用hadoop archive命令把小文件进行归档。
2.重建表,建表时减少reduce数量。
3.通过参数进行调节,设置map/reduce端的相关参数,如下:
设置map输入合并小文件的相关参数:
//每个Map最大输入大小(这个值决定了合并后文件的数量)
set mapred.max.split.size=256000000;
//一个节点上split的至少的大小(这个值决定了多个DataNode上的文件是否需要合并)
set mapred.min.split.size.per.node=100000000;
//一个交换机下split的至少的大小(这个值决定了多个交换机上的文件是否需要合并)
set mapred.min.split.size.per.rack=100000000;
//执行Map前进行小文件合并
set hive.input.format=org.apache.hadoop.hive.ql.io.CombineHiveInputFormat;
设置map输出和reduce输出进行合并的相关参数:
//设置map端输出进行合并,默认为true
set hive.merge.mapfiles = true
//设置reduce端输出进行合并,默认为false
set hive.merge.mapredfiles = true
//设置合并文件的大小
set hive.merge.size.per.task = 256*1000*1000
//当输出文件的平均大小小于该值时,启动一个独立的MapReduce任务进行文件merge。
set hive.merge.smallfiles.avgsize=16000000
是否合并Map输出文件:hive.merge.mapfiles=true(默认值为真)
是否合并Reduce 端输出文件:hive.merge.mapredfiles=false(默认值为假)
合并文件的大小:hive.merge.size.per.task=256*1000*1000(默认值为 256000000)
MapJoin简单说就是在Map阶段将小表读入内存,顺序扫描大表完成Join。
MapJoin分为两个阶段:
(1)通过MapReduce Local Task,将小表读入内存,生成HashTableFiles上传至Distributed Cache中,这里会对HashTableFiles进行压缩。
(2)MapReduce Job在Map阶段,每个Mapper从Distributed Cache读取HashTableFiles到内存中,顺序扫描大表,在Map阶段直接进行Join,将数据传递给下一个MapReduce任务。
也就是在map端进行join避免了shuffle。
可以使用列裁剪,分区裁剪,orc,parquet等存储格式
大数据场景下存储格式压缩格式尤为关键,可以提升计算速度,减少存储空间,降低网络io,磁盘io,所以要选择合适的压缩格式和存储格式
设置hive.execution.engine = tez;
向量化查询执行通过一次性批量执行1024行而不是每次单行执行,从而提高扫描,聚合,筛选器和连接等操作的性能。
在Hive 0.13中引入,此功能显着提高了查询执行时间,并可通过两个参数设置轻松启用:
设置hive.vectorized.execution.enabled = true;
设置hive.vectorized.execution.reduce.enabled = true;
对于大多数情况,Hive可以通过本地模式在单台机器上处理所有任务。
对于小数据,执行时间可以明显被缩短。通过set hive.exec.mode.local.auto=true(默认为false)设置本地模式。
set hive.exec.mode.local.auto;
hive.exec.mode.local.auto=false
Hive会将一个查询转化成一个或者多个阶段。这样的阶段可以是MapReduce阶段、抽样阶段、合并阶段、limit阶段。
默认情况下,Hive一次只会执行一个阶段,由于job包含多个阶段,而这些阶段并非完全互相依赖,
即:这些阶段可以并行执行,可以缩短整个job的执行时间。设置参数:set hive.exec.parallel=true,或者通过配置文件来完成。
set hive.exec.parallel;
hive.exec.parallel=false
Hive提供一个严格模式,可以防止用户执行那些可能产生意想不到的影响查询,通过设置
Hive.mapred.modestrict来完成
set Hive.mapred.modestrict;
Hive.mapred.modestrict is undefined
Hadoop通常是使用派生JVM来执行map和reduce任务的。这时JVM的启动过程可能会造成相当大的开销,
尤其是执行的job包含偶成百上千的task任务的情况。JVM重用可以使得JVM示例在同一个job中时候使用N此。
通过参数mapred.job.reuse.jvm.num.tasks来设置
Hadoop推测执行可以触发执行一些重复的任务,尽管因对重复的数据进行计算而导致消耗更多的计算资源,
不过这个功能的目标是通过加快获取单个task的结果以侦测执行慢的TaskTracker加入到没名单的方式来提高整体的任务执行效率。
Hadoop的推测执行功能由2个配置控制着,通过mapred-site.xml中配置
mapred.map.tasks.speculative.execution=true
mapred.reduce.tasks.speculative.execution=true