一、优化
1、查看执行计划Explain
1.查看简单的执行计划(常用)
explain select xxx from xxx;
2.查看详细的执行计划
explain extended select xxx from xxx;
3.语法
explain 【extended】【dependency】【authorization】 query-sql
2、分区、分桶、指定存储文件格式、压缩格式
常用文件存储格式: orc+snappy\parquet+lzop\orc+lzop
3、动态分区
需要进行一些设置:
set hive.exec.dynamic.partition=true;开启动态分区
set hive.exec.dynamic.partition.mode=nonstrict;设置为非严格模式
set hive.exec.max.dynamic.partitions=1000;所有mr节点的最大动态分区数
set hive.exec.max.dynamic.partitions.pernode=100;单个mr节点的最大动态分区数
set hive.exec.max.created.files=100000;整个mr任务可以创建的最大hdfs文件数
set hive.error.on.empty.partition=false;设置当有空分区生成时是否要报错
4、语法优化
一、针对单表查询的相关优化:
1.分区裁剪、列裁剪
2.group by
默认情况Map阶段的同一个key数据会分发到同一个reduce,当一个key数据过多时发生数据倾斜。
可以在Map端进行预聚合,有以下参数:
set hive.map.aggr=true;是否在map端进行聚合,默认true
set hive.groupby.mapaggr.checkinterval=100000;在map端进行聚合的数据量
set hive.groupby.skewindate=false;是否在数据倾斜时进行负载均衡,默认false
3.vectorization(矢量计算)
在计算scan、filter、aggregation时可以设置批处理(批量读取1-1024行),参数如下:
set hive.vectorized.execution.enabled=true;默认值false
set hive.vectorized.execution.reduce.enabled=true;
4.多重模式
在有多条sql都是对同一张表进行扫描,只是逻辑不同时,可使用多重模式进行优化:
列如:
insert ... select xxx from a where a.xxx = 'xxx';
...
insert ... select xxx from a where a.xxx = 'xxx';
可以修改为:
from a insert xxx select xxx where xxx = 'xxx' insert xxx select xxx where xxx = 'xxx'进而降低a表扫描次数
5.in/exists
在0.8以后开始支持in/exists语法,不推荐使用,可以使用left semi join进行替代,更高效
二、针对多表查询的相关优化:
join的时候,前面的表会被加载到内存中,而后面的表是进行磁盘扫描
1.CBO(成本优化器)
hive1.1.0之后,hive.cbo.enable默认是开启的,可以自动优化多个join的顺序,选择合适的join算法
2.谓词下推
默认hive.optimize.ppd是true,在sql语句中的where谓词逻辑都尽可能的提前
3.MapJoin(大小表join)
在join时将小表的数据分发加载到map端内存中,在map端完成join操作,规避reduce、数据倾斜,提升执行效率,有以下参数:
set hive.auto.convert.join=true;自动选择mapjoin,默认true
set hive.mapjoin.smalltable.filesize=25000000;设置小表阈值,默认小于25M
注意:在left join的时候MapJoin会失效,走的是reduce,commonjoin
4.SMBJoin(大表join大表)
使用分桶join,参数如下:
set hive.optimize.bucketmapjoin=true;
set hive.optimize.bucketmapjoin.sortedmerge=true;
set hive.input.format=org.apache.hadoop.hive.ql.io.bucketizedHiveInputFormat
5.笛卡尔积
默认hive是严格模式,动态分区以及笛卡尔积是不能执行的,如果是非严格模式,将会使用一个reduce完成多个表数据的笛卡尔积,数据量大,系统崩溃,善用笛卡尔积可以很容易解决一些实际的问题(遍历字符串每个字符、生成一个序列)
5、数据倾斜
在绝大多数的任务都完成的情况下,有少数任务执行很慢或者卡在99%甚至导致任务失败,那就是产生了数据倾斜,在一般情况下我们认为当最快的任务和最慢的任务执行时间相差20倍就可以认为是发生了数据倾斜,一般来说,数据倾斜的任务一般是伴随这分组操作,解决方案如下:
1.单表数据倾斜优化:
可以有单个key引起数据倾斜,也可以有多个key引起的数据倾斜,针对单个key引起数据倾斜一般就是开启map端聚合以及发生数据倾斜时自动负载均衡;如果多个key导致数据倾斜,需要我们额外去增加reduce的数量(调整每个reduce处理的数据量,或者在mapred-site.xml文件中设置mapreduce.job.reduces =xxx),是多key,还是单key我们可以采样,或者统计每个key的数据进行判断
set mapreduce.job.reduces = 15;
(1)每个Reduce处理的数据量默认是256MB
hive.exec.reducers.bytes.per.reducer=256000000
(2)每个任务最大的reduce数,默认为1009
hive.exec.reducers.max=1009
(3)计算reducer数的公式
N=min(参数2,总输入数据量/参数1)
2.join产生数据倾斜优化:
set hive.skewjoin.key=100000;指定join时key对应的最大记录条数,超出会拆分
set hive.optimize.skewjoin=false;是否开启join自动负载均衡
set hive.skewjoin.mapjoin.map.tasks=10000;第二个job的mapper数量
6、整体优化
增加map、ruduce的数量;fetch抓取;mapjoin;本地模式;并行执行;严格模式;jvm重用
7、Hive On Spark
几个小bug:
1.在表中的日期类型使用string,而不是date时,我们在查询时的子查询语句中使用date_diff()时会报空指针异常,需要修改字段类型为date,或者更换引擎。
2.开窗函数使用时,over(partition by xxx order by xxx rows between xxx and xxx),rows是失效的,永远都是从分组的起始行到当前行
二、HIVE源码
hive核心组成:元数据、client(cli、jdbc)、driver、解析器、编译器、优化器、执行器、计算引擎(mr、spark、tez)、HDFS
1、HQL怎么转换成MR任务
整体流程:
1.利用antlr定义的语法规则,对HQL完成语法解析,将HQL转换为AST
2.遍历AST,抽象出查询的基本组成单元QueryBlock,可以理解是最小的执行单元
3.遍历QueryBlock,将其转换为OperatorTree也就是逻辑执行计划,最小逻辑执行单元
4.使用逻辑优化器对OperatorTree进行逻辑优化,例如合并不必要的ReduceSinkOperator,减少shuffle数据量
5.遍历OperatorTree,转化为TaskTree,(翻译成MR任务的流程),逻辑计划转为物理计划
6.使用物理优化器对TaskTree进行物理优化(mapJoin\谓词下推...)
7.生成最终的执行计划,提交任务到Hadoop集群运行
2、涉及的角色
1.CliDriver:解析客户端命令参数...,定义标准输入输出流,按照';'切分HQL语句
2.Driver:将HQL语句转换为AST,将AST转换为TaskTree,提交任务执行
3.PaserDriver:HQL->AST,按照antlr规则将HQL转换为Token在解析生成AST
4.SemanticAnalyzer:将AST转换为QueryBlock,将QueryBlock转换为OperatorTree,对OperatorTree进行优化生成TaskTree,对TaskTree执行物理优化
5.ExecDriver:获取MR临时工作目录,定义Partitioner、Mapper、Reducer,进行Job实例化、提交
3、相关源代码
1.程序入口CliDriver.main() -> CliDriver.run()
首先会先获取命令行配置参数解析,如果参数不正确直接退出;否则,初始化输入输出流;然后对hive命令参数解析(-e\-f...)如果参数不正确直接退出;将参数进行封装到map,然后会获取hive.cli.prompt参数(影响后续hql获取解析),会根据hive.cli.prompt + databases + > 进行hql读取:
String line;
int ret = 0;
String prefix = "";
String curDB = getFormattedDb(conf,ss);
String curPrompt = prompt + curDB;
String dbSpaces = spacesForString(curDB);
while((line = reader.readLine(curPrompt + ">")) != null){
if(!prefix.equals("")){
prefix += "\n";
}
if(line.trim().startWith("--")){
continue;
}
if(line.trim().endsWith(";") && !line.trim().endsWith("\\;")){
line = prefix + line;
ret = cli.processLine(line,true);
prefix = "";
curDB = getFormattedDb(conf,ss);
curPrompt = prompt + curDB;
dbSpaces = dbSpaces.length() == curDB.length() ? dbSpaces : spacesForString(curDB);
}else{
prefix = prefix + line;
curPrompt = prompt2 + dbSpaces;
continue;
}
}
return ret;
读取到";"进行HQL解析cli.processLine(line,true),如果解析过程强制退出,打赢Ctrl+C退出jvm.否则会对HQL解析按照";"进行切分,进行HQL的拼接,如果命令不是exit、quit、source、!的任意一个开头就是我们写的HQL使用processLocalCmd()进行解析,会先获取当前时间,然后执行我们的HQL得到计算结果(qp.run(cmd).getResponseCode() -> compileInterval() -> excutor()编译、执行是同级方法;在compileInterval方法中做了三件事情,①解析器将HQL进行语法词法解析转化为AST,②编译器将AST转化为OperatorTree,③优化器得到优化后的OperatorTree、物理优化;excutor()中就是执行器),在此获取时间,然后打印我们的表头数据、执行结果、本次执行时间、抓取的记录条数...。
4、compileInterval()、excutor()详细过程
1.compileInterval()
此方法中的compile()是真正的编译过程,其中tree=ParseUtils.parse(command,ctx)完成HQL到AST的转化,sem.analyze(tree,ctx)完成AST->OperatorTree->物理计划的转换,
①ParseUtils.parse(command,ctx) -> parseDriver()
通过antlr的规则(5个文件、0.10版本以前是只有一个Hive.g文件,后期由于生成的java解析类可能超过java类文件上限,拆分成5个文件①词法规则HiveLexer.g②语法规则SeoectClauseParser.g、FromClauseParse.g、IdentifiersParser.g、HiveParser.g)解析将HQL解析为tokens(HiveLexerX()、TokenRewriteStream()),将tokens进行解析(HiveParser(tokens))进而得到AST(r.getTree())
②analyze
将AST转化为QueryBlock生成opTree,创建优化器,将很多的优化策略添加到上下文(mapjoin、semjoin、groupby...),执行优化,生成物理计划(mr,Tez,SparkCompiler)优化
2.execute()
构建任务,根据TaskTree构建MrJob,