Hive SQL的本质是将SQL语言映射到Hadoop分布式存储和计算框架上,将SQL语句转换成MapReduce程序进行执行,通常应用在海量数据的查询处理中,因此在使用中常常需要进行调优,以提高数据查询或处理效率。在平时工作中,影响数据处理效率的主要体现在数据倾斜、数据冗余、复杂sql等问题。在实际使用中需综合考虑多个影响因素并进行优化。本文主要记录下常见的优化点,后续若发现有其他相关内容,会继续补充。
1.排除某些列
可以使用正则表达式来排除某些列,前提需要将 hive.support.quoted.identifiers
值修改为none
。使用语法SELECT
(event_ts)?+.+ FROM <table>;
。
# 查看下参数当前值
==>set hive.support.quoted.identifiers;
+-----------------------------------------+
| set |
+-----------------------------------------+
| hive.support.quoted.identifiers=column |
+-----------------------------------------+
==>set hive.support.quoted.identifiers=none;
+---------------------------------------+
| set |
+---------------------------------------+
| hive.support.quoted.identifiers=none |
+---------------------------------------+
# 修改完成
# 测试查询除 age以外的字段
==> desc test_table;
+-----------+------------+----------+
| col_name | data_type | comment |
+-----------+------------+----------+
| id | string | |
| name | string | |
| age | string | |
+-----------+------------+----------+
# 执行查询语句
==> SELECT `(age)?+.+` FROM test_table t;
+-------+---------+
| t.id | t.name |
+-------+---------+
+-------+---------+
2.列裁剪和分区裁剪
其实就是在查询的时候,过滤掉不需要使用的列和分区,尤其是表的列数和分区数很大的时候,可以很好的减少读取开销,中间表存储开销和数据整合开销。
select id,name
from test_table where pt_d='2022-11-01'
裁剪所对应的参数项为:hive.optimize.cp=true(默认值为真) 分区参数为:hive.optimize.pruner=true(默认值为真)
解析阶段对应的则是ColumnPruner逻辑优化器
3.谓词下推
就是将SQL语句中的where谓词逻辑都尽可能提前执行,减少下游处理的数据量。对应逻辑优化器是PredicatePushDown,该优化器就是将OperatorTree中的FilterOperator向上提。
SELECT
t1.id
,t2.name
,t2.address
FROM test_a t1
LEFT OUTER JOIN
(
SELECT id
,name
,address
FROM test_b
WHERE pt_d='2022-11-01'
) t2
ON t1.id=t2.id;
4.排序
hive中的排序语法有4个,Sort By,Order By,Cluster By,Distrbute By
- Order By :会对输入做全局排序,因此只有一个reduce,性能较低
- Sort By:不是全局排序,其在数据进入reducer前完成排序, 只会保证每个reducer的输出有序
- Distrbute By:是控制在map端如何拆分数据给reduce端的,按照指定的字段对数据进行划分输出到不同的reduce中
- Cluster By:除了具有 distribute by 的功能外还兼具 sort by 的功能。排序只能是倒叙排序,不能指定排序规则为ASC或者DESC
实际应用中,更多使用Distrbute By与Sort By结合
SELECT
id
,create_time
FROM test_a
distrbute by id sort by create_time desc
5.去重
group by代替distinct,distinct通常在海量数据查询时效率比较低,这是由于按group by字段分组,按distinct字段排序,当group by字段是数量较少的枚举值时,会导致分配到较少的reduce个数,导致reduce负担较大,耗时增加。
SELECT COUNT(1)
FROM
(
SELECT
name
FROM test_a
group by name
)T
使用过程中需要结合实际情况使用,当存在类似分组字段时尽量改写distinct,否则不用。这里涉及到改写后会多启动一个mr,因此要确保数据量计算耗时要大于job启动耗时。
6.GROUP BY配置
- Map端聚合:并不是所有的聚合操作都集中在reduce进行,很多聚合操作是可以在Map端进行预聚合,然后再在reduce端再聚合输出
- hive.map.aggr=true,默认 true,对应的优化器为GroupByOptimizer
- hive.groupby.mapaggr.checkinterval=100000,设置map端预聚合的行数阈值,超过该值就会分拆job,默认值100000
- 数据倾斜时进行负载均衡
- hive.groupby.skewindata=false,默认false,使用时根据需要改为true,在group by时启动两个MR,第一个job会将map端数据随机输入reducer,每个reducer做部分聚合,相同的key就会分布在不同的reducer中。第二个job再将前面预处理过的数据按key聚合并输出结果,这样就起到了均衡的效果。
7.Join优化
7.1 小表前置
Hive在解析SQL时,会优先将join左边的表优先读进内存中,将小表放在join的左边,可以有效的减少内存溢出(OOM)的风险。
SELECT t1.id
FROM test_a t1
LEFT OUTER JOIN test_b t2
ON t1.id=t2.id
7.2 多表join时key相同
当出现多表join时,key相同时会合并成一个MR来处理
SELECT t1.id
FROM test_a t1
LEFT OUTER JOIN test_a t2
ON t1.id=t2.id
LEFT OUTER JOIN
LEFT OUTER JOIN test_c t3
ON t1.id=t3.id
若例子中的两个join中的条件不通,比如:t1.type=t3.type,就会多生成一个job
7.3 Map Join
就是将join操作在Map阶段完成,需要的数据在 Map 的过程中可以访问到则不再需要Reduce。 原理: 原始Join的MR任务之前创建一个新的MR本地任务,是将小表数据从 HDFS 上读取到内存中的哈希表中,读完后,将内存中的哈希表序列化为哈希表文件。在下一阶段,当MR任务启动时,会将这个哈希表文件上传到 Hadoop 分布式缓存中,该缓存会将这些文件发送到每个Mapper的本地磁盘上。所有 Mapper 都可以将此持久化的哈希表文件加载回内存,小表只需要读取一次。
SELECT
/*+ MAPJOIN(t1) */
t1.id
,count(t2.id) as num
FROM test_a t1
LEFT OUTER JOIN test_a t2
ON t1.id=t2.id
group by t1.id
;
也可通过参数设置,在执行时通过参数值判断,将Join转成Map Join。 参数:
- set hive.auto.convert.join = true; # 默认为false,参数为true时,Hive自动对左边的表进行统计,如果是小表就加入内存,即对小表使用Map join
- set hive.mapjoin.smalltable.filesize=25000000; 大表小表的阀值,默认值是25mb
- set hive.mapjoin.followby.gby.localtask.max.memory.usage=0.55,map join做group by 操作时,可以使用多大的内存来存储数据,如果数据太大,则不会保存在内存里
- set hive.mapjoin.localtask.max.memory.usage=0.9,本地任务可以使用内存的百分比
参考:blog.csdn.net/kwu_ganymed… cloud.tencent.com/developer/a…
8. 合并小文件
小文件:小于hdfs数据块的文件(hadoop1.:64M,hadoop2.:128M) 小文件的危害:容易在文件存储端造成瓶颈,给 HDFS 带来压力,影响处理效率 可以通过一些参数设置进行预防,在MR处理时对文件进行合并
-
set hive.input.format=org.apache.hadoop.hive.ql.io.CombineHiveInputFormat; 执行Map前进行小文件合并
-
set mapred.max.split.size=256000000; 每个Map最大输入大小(这个值决定了合并后文件的数量)
-
set mapred.min.split.size.per.node=100000000; 一个节点上split的至少的大小(这个值决定了多个DataNode上的文件是否需要合并)
-
set mapred.min.split.size.per.rack=100000000; 一个交换机下split的至少的大小(这个值决定了多个交换机上的文件是否需要合并)
-
set hive.merge.mapfiles = true, 设置map端输出进行合并,默认为true
-
set hive.merge.mapredfiles = true,设置reduce端输出进行合并,默认为false
-
set hive.merge.size.per.task = 25610001000, 设置合并文件的大小
-
set hive.merge.smallfiles.avgsize=1024000000, 当输出文件的平均大小小于1GB时,启动一个独立的map-reduce任务进行文件merge
9.数据倾斜
数据倾斜通常表现为:
任务进度长时间维持在99%(或100%),查看任务监控页面,发现只有少量(1个或几个)reduce子任务未完成。因为其处理的数据量和其他reduce差异过大
造成这个情况的原因一般主要涉及到两方面
- 一是任务中需要处理大量相同的key的数据
- 二是任务读取不可分割的大文件
具体的解决方案:
-
空值或无意义值
实际业务种有些大量的null或者无意义的数据参与其中,在表join时,触发shuffle被分配到一个reduce中了
方案:(1)过滤null和无意义的数据,不参与join操作;(2)给null的数据随机赋值,让其分配到不同的reduce中 -
不同数据类型
参与join的其中一个表的某个字段中存在不同数据类型string和int,导致在join时,hash会按int类型进行分配,这样所有string的都会被分配到一个reduce中
方案:将类型统一,转为string -
不可拆分的大文件
存在适用GZIP等不支持分割的压缩格式文件,这类文件只能被一个任务读取,导致数据读取时某个map成为瓶颈;
方案:转为支持分割的压缩文件格式:bzip2/zip,或者增加set mapreduce.map.memory.mb=4096;
,set mapreduce.map.java.opts=-Xmx3600m;
10.JVM重用
在MR job中,默认是每执行一个task就启动一个JVM。如果task非常小而碎,那么JVM启动和关闭的耗时就会很长。可以通过调节参数mapred.job.reuse.jvm.num.tasks
来重用。例如将这个参数设成5,那么就代表同一个MR job中顺序执行的5个task可以重复使用一个JVM,减少启动和关闭的开销。但它对不同MR job中的task无效。
11.各种模式
11.1 并行执行
Hive会将一个查询转化成一个或者多个阶段。可以是MR阶段、抽样阶段、合并阶段、limit阶段、或者Hive执行过程中可能需要的其他阶段。默认情况下一次只会执行一个阶段,但不乏有相互不怎么依赖,可以并行执行的阶段,这个时候可以通过参数调整。在数据量大,子查询较多的时候使用,否则没有必要。
set hive.exec.parallel=false;
默认false,需要可调整为trueset hive.exec.parallel.thread.number=8;
同一个sql允许最大并行度,默认为8
11.2 严格模式
主要是防止一些危险操作
set hive.strict.checks.no.partition.filter=false;
,限制分区表所有分区扫描,修改后必须在where中加上分区限制,否则不执行set hive.strict.checks.orderby.no.limit=false;
order by后面必须要加limit限制,否则不允许执行set hive.strict.checks.cartesian.product=false ;
限制笛卡尔积的查询
11.3 本地执行
Hive也可以不将任务提交到集群进行运算,而是直接在一台节点上处理。因为消除了提交到集群的overhead,所以比较适合数据量很小,且逻辑不复杂的任务。但需要做参数调整。
hive.exec.mode.local.auto=false;
默认false,关闭状态。需要可调整为truehive.exec.mode.local.auto.inputbytes.max=134217728;
默认128M,任务的输入数据总量必须小于该值,走本地模式hive.exec.mode.local.auto.tasks.max;
默认没有设置,最大输入文件个数,当输入文件个数小于这个值时采用本地模式
11.4Fetch抓取
Hive中对某些情况的查询可以不必使用MapReduce计算,例如:SELECT * FROM emp;
在这种情况下,Hive可以简单地读取emp对应的存储目录下的文件,然后输出查询结果到控制台。
参数hive.fetch.task.conversion=more
,表示:执行(无论是否有分区表)全局查找(select * )、字段查找、limit都不走mapreduce。
此外,该参数其他值:
- none:执行任何查询都走mapreduce;
- minimal:执行(单个分区表)全局查找(select * )、字段查找、limit不走mapreduce,不加分区字段执行会走MR