由于Hive是将符合SQL语法的字符串解析生成可以在Hadoop上执行的MapReduce的工具,所以运行速度十分感人,有时候甚至慢到你怀疑人生。但其实只要你掌握一些常见的简单调优手段,就可以大幅提高Hive跑数的速度。本文会介绍一些常用的Hive调优小技巧,给整个进阶篇画上一个句号。
使用Hive尽量按照分布式计算的一些特点来设计sql,和传统关系型数据库有区别,所以必须去掉原有关系型数据库下开发的一些固有思维。
在入门篇(十八)中已经介绍过with as的用法和优点,主要有以下几点:
多表join时尽早过滤数据,减少每个阶段的数据量,对于分区表必须通过子查询限制分区,同时只select需要使用的字段。
举例:
优化前写法:
SELECT a.platform
,a.app_version
,count(a.user_id) AS num
FROM app.t_od_use_cnt a
INNER JOIN app.t_od_new_user b
ON a.user_id = b.user_id
WHERE a.date_8 = 20190101
AND a.is_active = 1
AND b.date_8 = 20190101
GROUP BY platform
,app_version;
这种写法从语法上来说其实是没有错误的,但极其浪费资源,运行速度慢,因为运行时会先扫描两张表所有的数据,然后进行关联。最后才会通过where条件和select语句筛选出来所需数据。
优化后写法:
先进行分区剪裁和列剪裁,再进行关联操作:
SELECT a.platform
,a.app_version
,count(a.user_id) AS num
FROM (
--20190101的平台、版本、活跃用户id
SELECT platform
,app_version
,user_id
FROM app.t_od_use_cnt
WHERE date_8 = 20190101
AND is_active = 1
)a
INNER JOIN (
--20190101的新增用户id
SELECT user_id
FROM app.t_od_new_user
WHERE date_8 = 20190101
)b
ON a.user_id = b.user_id
GROUP BY platform
,app_version;
最终优化写法:
改进成with as写法:
WITH a
AS (
--20190101的平台、版本、活跃用户id
SELECT platform
,app_version
,user_id
FROM app.t_od_use_cnt
WHERE date_8 = 20190101
AND is_active = 1
)
,b
AS (
--20190101的新增用户id
SELECT user_id
FROM app.t_od_new_user
WHERE date_8 = 20190101
)
--20190101的平台、版本、新增活跃用户数
SELECT a.platform
,a.app_version
,count(a.user_id) AS num
FROM a
INNER JOIN b
ON a.user_id = b.user_id
GROUP BY platform
,app_version;
如果select和where后对同一个字段要使用同样的函数进行处理,那么先在select中进行处理,然后套一层子查询进行where条件限制。因为复杂函数十分耗费资源,能只用一遍就只用一遍。
举例:
想要取出20190101的奇数尾号用户id
优化前:
SELECT date8
,PARSE_URL('http://a.qq.com' || url, 'QUERY', 'uid') AS uid
FROM t1
WHERE PARSE_URL('http://a.qq.com' || url, 'QUERY', 'uid') % 2 = 1
AND date8 = '20190101'
PARSE_URL函数的用途是解析url,是一个很浪费资源的函数,所以尽量避免在where和select中二次使用。
优化后:
SELECT date8
,uid
FROM (
SELECT date8
,PARSE_URL('http://a.qq.com' || url, 'QUERY', 'uid') AS uid
FROM t1
WHERE date8 = '20190101'
) a
WHERE uid % 2 = 1
这种优化只有在数据量较大时才会体现出效果,因为优化前的写法只有一个job,而优化后出现了子查询变成了两个job,如果数据量较小,启动job的时间反而会长于函数运算的时间。
函数嵌套层数过多会大幅拖慢运行速度,多熟悉Hive各种函数的用法(多看几遍进阶篇,哈哈),实现方式往往不只一种,选最简单的方法实现需求。
使用子查询或者with as语句创建临时视图时,最好对每一块数据集进行去重处理,去重时使用group by,不要使用distinct。去重是为了提前缩小数据量,使用group by不用distinct是因为数据量大时group by去重更快。
大表和小表关联时,将数据量小的表放在join左边。原因是在 join 操作的 Reduce 阶段,位于 join左边的表会被加载进内存,将条目少的表放在左边,可以有效减少发生内存溢出错误的几率。
关联时数据量小的表放在join左边
SELECT a.col
,b.col
FROM 小表 a
INNER JOIN 大表 b
ON a.key = b.key1
join查找操作中如果存在多个join,且所有参与join的表中其参与join的key都相同,则会将所有的join合并到一个mapreduce程序中。 最优的关联方式是所有表都直接要和主表关联,且key保持一致。
举例:
关联时都关联同一个表,并且on后面的key一致,这样会在一个mapreduce程序中执行join
SELECT a.col
,b.col
,c.col
FROM a
INNER JOIN b
ON a.key = b.key1
INNER JOIN c
ON a.key = c.key1;
关联时没有都关联同一个表,并且on后面的key不一致,会在两个mapreduce程序中执行join,拖慢运行速度
SELECT a.col
,b.col
,c.col
FROM a
INNER JOIN b
ON a.key = b.key1
INNER JOIN c
ON c.key = b.key2;
MapJoin是在Map阶段进行表之间的连接。而不需要进入到Reduce阶段才进行连接。这样就节省了在Shuffle阶段时要进行的大量数据传输。从而起到了优化作业的作用。适用于一个很大的表关联一个很小的表,这个小表可以存放在内存中而不影响性能。这样我们就把小表文件复制到每一个Map任务的本地,再让Map把文件读到内存中待用。
在Hive 0.7之前,需要使用 /*+ mapjoin(table) */才会执行MapJoin 。但是Hive v0.7之后的版本已经不需要手动设置就会进行优化。默认小表小于25M就会开启MapJoin优化。
设置参数为:set hive.auto.convert.join=true; 默认为true。
抽取数据的时候使用核心表去left join其他表,保证核心表中所有数据都在,即使匹配不到也会存在Null而不是数据的丢失。
使用核心表去left join其他表
SELECT a.col
,b.col
FROM 核心表 a
LEFT JOIN 其他表 b
ON a.key = b.key1
数据倾斜是指:并行处理的数据集中,某一部分数据显著多于其它部分,从而使得该部分的处理速度成为整个数据集处理的瓶颈。 表现为整体任务基本完成,但仍有少量子任务的reduce还在运行。进度卡在99%或100%一直无法完成。
造成数据倾斜的原因一般有以下三种:
1. 大表与大表关联,如果用来连接的字段(on后面的)null值过多,这些null值都会由一个reduce处理。
解决方法:
1)如果null值所对应的的数据都不需要的话,可以直接在on后面将表中的null值过滤掉。
SELECT *
FROM a
INNER JOIN b
ON a.user_id IS NOT NULL
AND a.user_id = b.user_id
2)如果null值所对应的的数据仍然需要的话, 可以将null值替换为随机数进行关联。
SELECT *
FROM a
LEFT JOIN b
ON CASE
WHEN a.user_id IS NULL
THEN CONCAT ('hive',RAND())
ELSE a.user_id
END = b.user_id;
2. group by时分组的维度过少,每个维度的值过多,导致处理某值的reduce耗时很久,比如根据性别进行group by,只有男女两类,每个类别里的数据量就可能过大。
解决方法:
设置下如下参数:
参数设置 | 默认值及含义 |
set hive.map.aggr=true; | 是否在 Map 端进行聚合,默认为true |
set hive.groupby.mapaggr.checkinterval=1000000; | 在 Map 端进行聚合操作的条目数目,默认为10万条 |
set hive.groupby.skewindata=true; | 数据倾斜聚合优化,用于负载均衡。默认 false |
3.count(distinct col)时col中某个特殊值相同的过多,处理特殊值耗时。
解决方法:
先用group by去重,然后嵌套子查询用count(1)
优化前写法:
SELECT platform
,count(DISTINCT version) num
FROM a
GROUP BY platform;
优化后写法:
SELECT platform
,count(1) num
FROM (
SELECT platform
,version
FROM a
GROUP BY platform
,version
) a
GROUP BY platform;
常用优化参数如下表,除了第三条和最后一条,其余参数建议直接写进家目录的.hiverc文件中,每次启动hive自动加载这些参数设置:
参数设置 | 默认值及含义 |
set hive.map.aggr=true; | 是否在 Map 端进行聚合,默认为true |
set hive.groupby.mapaggr.checkinterval=1000000; | 在 Map 端进行聚合操作的条目数目,默认为10万条 |
set hive.groupby.skewindata=true; | 数据倾斜聚合优化,用于负载均衡。默认 false。开启后一个select语句中只能使用一个count(distinct)。多于一个会报错。 |
set hive.optimize.groupby=true; | 优化group by,默认为true |
set hive.optimize.union.remove=true; | 在大量union情况下进行优化,默认false,需要带有partition的表才能用 |
set hive.exec.parallel=true; | 并行执行嵌套select,默认 false,强烈建议开启 |
set hive.exec.parallel.thread.number=16; | 执行嵌套sql最大并行数,默认为8,可以调成16 |
set hive.exec.mode.local.auto=true; | 开启本地模式,在单台机器上处理所有的任务。对于小数据集,执行时间会明显被缩短。默认为false 当一个job满足如下条件才能真正使用本地模式: |
能看到这里的同学,就右上角点个赞吧,3Q~