在执行SQL之前的优化,其实可以分为两个部分:文件存储优化和Hive的参数优化。
首先来看文件存储优化,文件存储优化主要的想法是想在一定程度上对数据进行压缩。在Hive中文件存储格式有TextFile、SequenceFile、RCFile、ORC(实际工作中常用)、parquet五种类型,但是ORC是最常用的,对于这五种类型用下表可以清晰对之进行区分。
对于TextFile(默认的数据格式)耗费存储空间,I/O性能较低这一点是因为MapReduce需要进行数据的传输,会占用带宽,不进行压缩的话,带宽占比就会特别高。那怎么指定文件存储格式?其实就是在建表的时候指定文件格式,创建ORC存储格式如下:
CREATE TABLE T1(
'USER_ID' String,
)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY '\t'
LINES TERMINATED BY '\n'
STORE AS ORC;
在Hive中其实有很多的参数可以设置,可以在一定程度上使得SQL执行效率会得到提高。我们每次在运行一个Hive的Job的时候,总是会出现下面的一段话:
Number of reduce tasks not specified. Estimated from input data size: 1
In order to change the average load for a reducer (in bytes):
set hive.exec.reducers.bytes.per.reducer=
In order to limit the maximum number of reducers:
set hive.exec.reducers.max=
In order to set a constant number of reducers:
set mapreduce.job.reduces=
这也是提示了我们通过设置这三个参数,可以进行优化,先来分别看看这三个参数:
hive.exec.reducers.bytes.per.reducer=
hive.exec.reducers.max=
mapreduce.job.reduces=
这三个参数都涉及了Reduce的个数问题,当然有一个先后顺序,优先级最高的就是mapreduce.job.reduces,只要设置这个参数,会优先使用这个参数。
除了上面的参数,还有有一个针对于数据倾斜可以设置的一个参数。读到这里,你肯定会疑惑什么是数据倾斜?最直观的现象就是你在控制台看到整个任务执行的状态类似于如下形式:
map=100%,reduce=56%
map=100%,reduce=56%
map=100%,reduce=56%
map=100%,reduce=56%
。。。。。。。。。。
即整个任务都会卡在reduce那里,而且时间会很长,除了这种看日志的形式,还可以看任务执行时间,如果执行时间过长,多半都有可能是数据倾斜所致。因为如果出现数据倾斜,任务直接失败,然后调度到另一个reduce处理,但是还是失败,尝试调度,直接导致任务一直出问题,运行时间很长,直接导致整个任务运行过程,需要进行变量设置——(set hive.groupby.skewindata =true)。
数据倾斜的场景:这里有数据条数1亿条,比如异常数据、脏数据落假设有5000W条,其他数据5000w条,脏数据和异常数据会落到一个reduce中。
设置参数(hive.groupby.skewindata)主要有两个目的:
1)将MapReduce进行拆分为两个mapReduce处理
2) 第一步,随机分发到不同的reduce节点,进行聚合(200w条记录,count=》1条)
第二步,最终一个reduce做最后结果的聚合(200条记录,求和)
总之,数据倾斜出现的原因: key分布不均匀导致、人为的建表疏忽(NULL值等)、业务数据特点(用户注册时敏感信息为空)。因此也可在进行数据录入、抽取的时候就设置默认值等方式。
然后还有并行执行的参数,同步执行hive的多个阶段,Hive在执行过程中,将一个查询转换成一个或者多个阶段。某个特定的JOB可能会包含众多的阶段,而这些阶段可能并非完全相互依赖,也就是说可以并行执行的,这样可以使整个Job的执行时间缩短:set hive.exec.parallel = true。
除了上面的这些上面参数设置,还可以针对于Map或者Reduce的参数优化。
Map端虽然可以进行参数的优化,但是一般都没有对其进行设置,一般都会在Reduce端进行设置,因为Reduce端参数是更影响执行效率的一部分。
1)dfs.block.size。通过设置该参数,可以使通过Input输入的数据产生一个或者多个Map任务。
2)合并小的文件,减少Map数。
set mapred.map.split.size ;
set mapred.min.split.size.per.node;
set mapred.min.split.size.per.rack;
set hive.input.format=org.apache.hadoop.hive.ql.io.CombineHiveInputFormat;
3)适当增加Map的个数:set mapred.map.task。
4)打开Map端的聚合:hive.map.aggr=true。类似于MapReduce中Combiner
Reduce端的参数设置优化是最常见的,上面任务运行提示的三个参数都是与Reduce有关。所以就不在这里多说,除了上面的参数,还有以下的:
调整reduce个数:set mapred.reduce.task=10,这个其实和mapred.job.reduce参数是一样的,默认值为1。
其实一个reduce会特别影响效率,但是有的情况只有一个Reduce,主要有以下的三种情况:
1)没有group by
2)有order by
3) 笛卡尔积
1) 工作中针对数据去重,能使用group by 就不使用distinct。使用group by 会使用多个Reduce进行处理。而distinct,所有的数据会在一个reduce中进行处理。
2) 分区裁剪。where 中分区条件,会提前生效,不必特意做子查询,直接Join和GroupBy
例如:求星期一的下单数量:
-- 不推荐
select count(*) order_cnt from orders ord inner join trains tr on ord.order_id=tr.order_id where order_dow='0'
-- 推荐一:
select count(*) order_cnt from orders ord inner trains tr on (ord.order_id=tr.order_id and order_dow='0') limit 10;
-- 推荐二:
select count(*) order_cnt from (select * from orders where order_dow='0') ord inner trains tr on (ord.order_id=tr.order_id and order_dow='0') limit 10;
3) 笛卡尔积,在使用笛卡尔积必须注意以下内容:
① Join的时候不见on条件,或者无效的on条件时,Hive只能使用一个Reducer来完成笛卡尔积
② 使用MAPJOIN,这个会优先将表加载到内存中,但是这个只是针对小表【不超过1G或者50万条记录】,所以MAPJOIN后的表必须为小表。MAPJOIN会把小表全部读取到内存中,在Map阶段直接拿另外一个表的数据和内存中的表数据做匹配,由于在map是进行了join操作,省去了reduce运行的效率也会高很多。
select /*+MAPJOIN(aisles) */ a.aisle as aisle_name,b.product_name from aisles a inner join products b on a.aisle_id=b.aisle_id limit 10;
③ Union all/distinct。先做union all 再做join或者group by 等操作可以有效减少MR过程,尽管多个Select,最终只有一个MR。【Union 会直接去重】。
④ 表的连接顺序,按照Join顺序中最后一个表应该尽量是大表,因为JOIN前一阶段生成的数据会存在于Reduce的Buffer中,通过Stream最后的表,直接从Redcucer的buffer中读取已经缓冲的中间结果数据,这样,与后面的大表进行连接时,只需要从buffer中读取缓存的key,与大表中指定的Key进行连接,速度会更快,也可能避免内存缓冲区溢出。即如下这种形式:
⑤ 一般Join的时候,会将Where后的条件放在ON后,因为在Join过程中,就对不满足条件的记录进行了预先过滤。