class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="2"> class="hljs-ln-code"> class="hljs-ln-line">( timestamp date, class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="3"> class="hljs-ln-code"> class="hljs-ln-line">sessionID string, class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="4"> class="hljs-ln-code"> class="hljs-ln-line">url string, class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="5"> class="hljs-ln-code"> class="hljs-ln-line">source_ip string ) class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="6"> class="hljs-ln-code"> class="hljs-ln-line">STORED as ORC class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="7"> class="hljs-ln-code"> class="hljs-ln-line">tblproperties (“orc.compress" = "SNAPPY"); class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="8"> class="hljs-ln-code"> class="hljs-ln-line"> class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="9"> class="hljs-ln-code"> class="hljs-ln-line">比如上边这个表,如果要获取每一个sessionID最新的访问记录,可以这样写: class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="10"> class="hljs-ln-code"> class="hljs-ln-line"> class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="11"> class="hljs-ln-code"> class="hljs-ln-line">SELECT clicks.* class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="12"> class="hljs-ln-code"> class="hljs-ln-line">FROM clicks inner join ( select sessionID, max(timestamp) as max_ts class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="13"> class="hljs-ln-code"> class="hljs-ln-line">from clicks class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="14"> class="hljs-ln-code"> class="hljs-ln-line">group by sessionID) latest class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="15"> class="hljs-ln-code"> class="hljs-ln-line">ON clicks.sessionID = latest.sessionID and clicks.timestamp = latest.max_ts; class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}" onclick="hljs.signin(event)">
语句解释:
查看执行计划会发现该语句会产生两个MapReduce,第一个MapReduce生成最新的sessionID信息表,第二MapReduce做一个Join,前面输出的结果与原表进行Join得到最后的结果。
再来看看下边的写法:
- class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="1"> class="hljs-ln-code"> class="hljs-ln-line">SELECT *
- class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="2"> class="hljs-ln-code"> class="hljs-ln-line">FROM ( SELECT *,
- class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="3"> class="hljs-ln-code"> class="hljs-ln-line">RANK() over (partition by sessionID, order by timestamp desc) as rank
- class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="4"> class="hljs-ln-code"> class="hljs-ln-line">FROM clicks
- class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="5"> class="hljs-ln-code"> class="hljs-ln-line">) ranked_clicks
- class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="6"> class="hljs-ln-code"> class="hljs-ln-line">WHERE ranked_clicks.rank=1;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}" onclick="hljs.signin(event)">
语句解释:
查看执行计划会发现只会有一个MapReduce,该语句将数据读出来对sesssionID进行分组并按时间进行倒序排序,最后过滤出排序后的第一条记录。
比较第一个语句和第二个语句,发现第二个语句减少了一次MapReduce操作,性能自然大幅提升。很明显第一个语句多余执行了一次Join操作,第二语句以分组排序替代了Join,除去了不必要的Join操作,带来了性能的提升。
5.2.2.3 Distinct聚合优化
操作场景
SELECT COUNT( DISTINCT id ) FROM TABLE_NAME ;
优化前的问题:只有一个reduce处理全量数据,并发度不够,存在单点瓶颈。

换种写法,reduce就会有多个,性能提升很多。
SELECT COUNT(*) FROM (SELECT DISTINCT id FROM TABLE_NAME ) t;

5.2.2.4 Order By
操作场景
默认情况下,Order By会生成一个Reduce进行全局排序,所以,一般要求Order By要加上Limit来使用,或者修改为Distribute By和Sort By 。
对于必须进行全量,全局排序的,可以考虑对数据进行抽样后,通过Distribute By和Sort By进行并行排序。
修改参数
class="table-box">参数名 | 描述 |
---|
hive.optimize.sampling.orderby | 默认是false |
hive.optimize.sampling.orderby.number | 默认1000,抽取多少个样本 |
hive.optimize.sampling.orderby.percent | 默认0.1,每条记录生成一个随机数,当随机数小于这个值时,选中该记录 |
5.2.2.5 Multi Insert
对于从同一份源表往不同的表中插数据,可以采用multi insert语法,减少job个数
- class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="1"> class="hljs-ln-code"> class="hljs-ln-line">FROM (SELECT a.status, b.school, b.gender
- class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="2"> class="hljs-ln-code"> class="hljs-ln-line"> FROM status_updates a JOIN profiles b
- class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="3"> class="hljs-ln-code"> class="hljs-ln-line">
- class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="4"> class="hljs-ln-code"> class="hljs-ln-line">
- class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="5"> class="hljs-ln-code"> class="hljs-ln-line"> ON (a.userid = b.userid anda.ds='2009-03-20' )
- class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="6"> class="hljs-ln-code"> class="hljs-ln-line"> ) subq1
- class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="7"> class="hljs-ln-code"> class="hljs-ln-line">
- class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="8"> class="hljs-ln-code"> class="hljs-ln-line">
- class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="9"> class="hljs-ln-code"> class="hljs-ln-line">INSERT OVERWRITE TABLE gender_summary PARTITION(ds='2009-03-20')
- class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="10"> class="hljs-ln-code"> class="hljs-ln-line"> SELECT subq1.gender, COUNT(1) GROUP BY subq1.gender
- class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="11"> class="hljs-ln-code"> class="hljs-ln-line">
- class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="12"> class="hljs-ln-code"> class="hljs-ln-line">
- class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="13"> class="hljs-ln-code"> class="hljs-ln-line">INSERT OVERWRITE TABLE school_summary PARTITION(ds='2009-03-20')
- class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="14"> class="hljs-ln-code"> class="hljs-ln-line"> SELECT subq1.school, COUNT(1) GROUP BY subq1.school
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}" onclick="hljs.signin(event)">
上述查询语句使用了Multi-Insert特性连续insert了两张不同的表,减少了一轮MapReduce操作。
5.2.2.6 动态分区+distribute by
一般在从text转orc的时候,会用到动态分区,对于
- class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="1"> class="hljs-ln-code"> class="hljs-ln-line">INSERT OVERWRITE TABLE T1 PARTITION(P1)
- class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="2"> class="hljs-ln-code"> class="hljs-ln-line">
- class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="3"> class="hljs-ln-code"> class="hljs-ln-line">
- class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="4"> class="hljs-ln-code"> class="hljs-ln-line">
- class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="5"> class="hljs-ln-code"> class="hljs-ln-line">
- class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="6"> class="hljs-ln-code"> class="hljs-ln-line">
- class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="7"> class="hljs-ln-code"> class="hljs-ln-line"> SELECT C1,C2,P1FROM T2
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}" onclick="hljs.signin(event)">
最好对分区字段加上distribute by,否则最坏的情况就是,一个Task包含每个分区的数据,往每个分区插入数据,导致Task的内存溢出。
- class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="1"> class="hljs-ln-code"> class="hljs-ln-line">INSERT OVERWRITE TABLE T1 PARTITION(P1)
- class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="2"> class="hljs-ln-code"> class="hljs-ln-line">
- class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="3"> class="hljs-ln-code"> class="hljs-ln-line">
- class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="4"> class="hljs-ln-code"> class="hljs-ln-line">
- class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="5"> class="hljs-ln-code"> class="hljs-ln-line">SELECT C1, C2, P1 FROM T2 DISTRIBUTE BY P1
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}" onclick="hljs.signin(event)">
6.1 性能调优常用方法
6.1.1 表模型优化
6.1.1.1 数据类型选择
操作场景
避免使用复杂的MAP,STRUCT等类型。
字段类型要精确,例如是INTEGER型的,不要定义成STRING。精确的类型可以让计算机CPU更快的处理。
6.1.1.2 表分区
操作场景
将数据分区,有助于减少查询时的数据量读取。例如按照天分区,那么按照时间跨度的查询,就可以通过排除掉一些分区,减少需要读取的文件数。
分区支持多层,即对多个字段分区,支持动态分区,使用动态分区请参考7.4.2.5 Multi Insert章节的注意事项;
单表的分区要尽量控制在1万以内,分区太多,会导致产生很多小文件,获取分区信息耗时等问题;
修改参数
class="table-box">参数名 | 优化描述 |
---|
hive.exec.dynamic.partition.mode | 默认值strict,修改值nonstrict; |
hive.exec.max.dynamic.partitions | 默认值1000,最大动态partition的数量,可以根据分区的大小进行修改; |
hive.exec.max.created.files | 默认值100000,一个MR job中最多可以创建文件的数量; |
6.2.1 文件格式
操作场景
表存储的文件格式有文本格式,有列存储类型的格式,对于Hive,建议使用列存储类型ORC File,虽然转化成ORC文件类型,需要多一些时间,但是,会让后边查询操作节省大量的时间。
修改参数
class="table-box">参数名 | 优化描述 |
---|
orc.compress | 默认值ZLIB |
6.3.1 小文件
操作场景
现在社区对于输入是小文件是没有自动合并功能,提供的合并命令,不够实用, 比如一个表有很多分区,需要对每个分区执行合并操作,我们暂时不建议用户用。
现在如果输入有小文件要合并,我们是建议用户先原始数据加载到hive表中,再启动一个MR从小文件临时表插入最终表,这个过程即解决小文件也解决文件存储格式(ORC + Snappy)。
对于输出,现在hive有参数控制多小的文件是小文件,对于输出的小文件是否要进行合并的参数,如下。
对于顺序执行的作业链,只有最后一张表的数据需要持久化,中间临时结果用完就删除的情况,可以在最后生成结果表之前开启下面参数,防止之前的作业也会生成合并任务使作业变慢。
修改参数
class="table-box">参数名 | 描述 |
---|
hive.merge.mapfiles | 默认为 True,是否合并 Map 输出文件 |
hive.merge.mapredfiles | 默认为 False,是否合并 Reduce 输出文件 |
hive.merge.size.per.task | 默认25610001000,合并后单个文件的大小 |
hive.merge.smallfiles.avgsize | 默认16 * 1000 * 1000,文件平均大小小于该值时,认为需要合并 |
6.4.1 压缩格式
操作场景
通常来说压缩会对性能有提升,虽然消耗了一点CPU,但是节省了磁盘IO,节省了网络带宽。
对于ORC File文件,在定义表的时候,就指定了压缩类型。
对于中间结果,一般是Sequence File类型,因此可以指定中间文件的压缩类型和压缩算法;
修改参数
class="table-box">参数名 | 描述 |
---|
hive.exec.compress.output | 默认是false;对于文本文件格式,可以指定修改为true;orc文件类型不受此参数控制; |
mapred.output.compression.codec | 默认是org.apache.hadoop.io.compress.DeflateCodec,建议改成org.apache.hadoop.io.compress.SnappyCodec |
hive.exec.compress.intermediate | 中间结果是否进行压缩,默认是false |
hive.intermediate.compression.codec | 默认为空,建议改成org.apache.hadoop.io.compress.SnappyCodec |
6.5.1 并行度控制
操作场景
SQL会转换成Map和Reduce,Map的数量由总数据量除以Map处理的数据量来定,Reduce的数量是总数据量除以Reduce处理的数量,不超过最大值。
修改参数
class="table-box">参数名 | 描述 |
---|
mapreduce.input.fileinputformat.split.maxsize | 默认256000000,map处理的最大数据量,一般不用改 |
hive.exec.reducers.bytes.per.reducer | 默认256000000,reduce处理的最大数据量,一般不用改 |
hive.exec.reducers.max | 默认999,对于集群比较大的情况,可以适当改大。 |
6.6.1Task内存
操作场景
默认情况下,每个Map和Reduce使用的最大内存都是4GB,堆内存是3GB。
原因如下: (说明格式:虽然分配了4G,但有时候任务实际可能只会到2G)
1. hive当前每个map处理的数据量是这个参数mapreduce.input.fileinputformat.split.maxsize控制的, 默认是256MB, 通常情况下,这个256MB是压缩文件,由于压缩文件压缩率不定,解压后数据量不同,对内存就会有不同的需求。
2. Hive有Mapjoin,Mapjoin是把小表加载到Map的内存中, 需要估计好加载后的内存,防止加载到内存过大导致内存溢出。
3. 数据可能有些微的倾斜情况,导致Reduce等处理的数据量变大,如果内存小会导致失败。
4. 对于大内存机器,可以尽量使用大的内存降低从内存刷磁盘的频率,这样可以减少IO。正确的做法应该是基于job的情况设置合适的内存,但是考虑到这样业务就需要花费更多的时间去调优, 因此配置成一个更大一点,更通用的值,保证job运行的稳定性。
修改参数
class="table-box">参数名 | 描述 |
---|
mapreduce.map.memory.mb | 默认4096,Map的YARN内存申请数量,单位MB |
mapreduce.reduce.memory.mb | 默认4096,Reduce的YARN内存申请数量,单位mb |
mapreduce.map.java.opts | 默认-Xmx2048M -Djava.net.preferIPv4Stack=true,Map的java堆大小 |
mapreduce.reduce.java.opts | 默认-Xmx3276M -Djava.net.preferIPv4Stack=true,Reduce的java堆大小 |
yarn.app.mapreduce.am.resource.mb | 默认1536,MapReduce引擎下,AM占用的YARN内存资源总数,必须不小于堆大小设置 |
yarn.app.mapreduce.am.command-opts | 默认值-Xmx1024m -XX:CMSFullGCsBeforeCompaction=1 -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -verbose:gc任务task数很多时,比如task超过3万时,需要考虑增加AM的内存,以免AM由于内存不足拖慢整个任务,可以把Xmx改成3072m;MapReduce引擎下,AM 堆大小设置 |
7.1 其他性能调优常用方法
7.1.1 启用CBO
操作场景
Hive默认是逻辑优化器,在多表join的情况下可以考虑启用CBO(cost based optimization),hive将会根据已经收集好的表统计信息,自动选择代价较小的join顺序来执行。
CBO需要对表进行analysis,详细参考CPI文档。
修改参数
class="table-box">*参数名* | *描述* |
---|
hive.cbo.enable | 默认是false,启用cbo需要修改成true; |
7.2.1 启用向量化
操作场景
现代CPU一般都支持SIMD(Single instruction, multiple data)等指令。通过向量化,可以使Hive利用现代CPU的这些高级特性,获得性能的提升
修改参数
class="table-box">*参数名* | *描述* |
---|
hive.vectorized.execution.reduce.enabled | 默认是false,启用需要修改成true; |
最后
谢谢大家 整理较为详细 推荐收藏哟
@程序员500佰
评论记录:
回复评论: