Hive优化

  • 一、hive参数优化
  • 1、map数优化
  • 2、reduce数优化
  • 3、Fetch抓取(Hive可以避免进行MapReduce)
  • 4 、模式选择
  •  1)本地模式
  •  2)并行模式
  •  3)严格模式
  • 5、JVM重用
  • 6、推测执行
  • 7、并行执行
  • 8、合并小文件

  • 二、hive压缩存储优化
  • 1、压缩优化
  •  1)压缩原因
  •  2)压缩方式
  •  3)压缩方式的选择
  •  4)压缩的使用
  • 2、存储优化
  •  1)存储格式
  • 三、hive表设计优化
  • 1、内部表 & 外部表
  • 2、分区
  •  1)静态分区
  •  2)动态分区
  • 3、分桶
  • 四、SQL优化
  • 1、小表join大表
  • 2、大表join大表
  •  1)空key过滤
  •  2)空key转换
  • 3、mapjoin
  • 4、group by
  • 5、笛卡尔积
  • 6、count(distinct)去重统计
  • 7、行列过滤
  • 8、排序选择
  • 9、查看sql的执行计划(Explain)
  • 五、数据倾斜


一、hive参数优化

1、map数优化

mapred.min.split.size: 指的是数据的最小分割单元大小;min的默认值是1B
mapred.max.split.size: 指的是数据的最大分割单元大小;max的默认值是256MB
通过调整max可以起到调整map数的作用,减小max可以增加map数,增大max可以减少map数。
需要提醒的是,直接调整mapred.map.tasks这个参数是没有效果的。

举例:

  a) 假设input目录下有1个文件a,大小为780M,那么hadoop会将该文件a分隔成7个块(6个128M的块和1个12M的块),从而产生7个map;

  b) 假设input目录下有3个文件a,b,c,大小分别为10M,20M,130M,那么hadoop会分隔成4个块(10M,20M,128M,2M),从而产生4个map数;

  注意:如果文件大于块大小(128M),那么会拆分,如果小于块大小,则把该文件当成一个块。

  其实这就涉及到小文件的问题:如果一个任务有很多小文件(远远小于块大小128M),则每个小文件也会当做一个块,用一个map任务来完成。

  而一个map任务启动和初始化的时间远远大于逻辑处理的时间,就会造成很大的资源浪费。而且,同时可执行的map数是受限的。那么,是不是保证每个map处理接近128M的文件块,就高枕无忧了?答案也是不一定。比如有一个127M的文件,正常会用一个map去完成,但这个文件只有一个或者两个小字段,却有几千万的记录,如果map处理的逻辑比较复杂,用一个map任务去做,肯定也比较耗时。

  我们该如何去解决呢???

  我们需要采取两种方式来解决:即减少map数增加map数。

  1、减少map数量

假设一个SQL任务:
Select count(1) from popt_tbaccountcopy_mes from pt = '2012-07-04';
该任务的inputdir :  /group/p_sdo_data/p_sdo_data_etl/pt/popt_tbaccountcopy_mes/pt=2012-07-04
共有194个文件,其中很多是远远小于128M的小文件,总大小9G,正常执行会用194个map任务。
Map总共消耗的计算资源:SLOTS_MILLIS_MAPS= 623,020
通过以下方法来在map执行前合并小文件,减少map数:
set mapred.max.split.size=100000000;
set mapred.min.split.size.per.node=100000000;
set mapred.min.split.size.per.rack=100000000;
set hive.input.format=org.apache.hadoop.hive.ql.io.CombineHiveInputFormat;
再执行上面的语句,用了74个map任务,map消耗的计算资源:SLOTS_MILLIS_MAPS= 333,500
对于这个简单SQL任务,执行时间上可能差不多,但节省了一半的计算资源。
大概解释一下,100000000表示100M,
set hive.input.format=org.apache.hadoop.hive.ql.io.CombineHiveInputFormat;这个参数表示执行前进行小文件合并,
前面三个参数确定合并文件块的大小,大于文件块大小128m的,按照128m来分隔,
小于128m,大于100m的,按照100m来分隔,把那些小于100m的(包括小文件和分隔大文件剩下的),进行合并,最终生成了74个块。

2、增大map数量

  如何适当的增加map数?
  当input的文件都很大,任务逻辑复杂,map执行非常慢的时候,可以考虑增加Map数,来使得每个map处理的数据量减少,从而提高任务的执行效率。

假设有这样一个任务:
    Select data_desc,
               count(1),
               count(distinct id),
               sum(case when ...),
               sum(case when ...),
               sum(...)
    from a group by data_desc

  如果表a只有一个文件,大小为120M,但包含几千万的记录,如果用1个map去完成这个任务,肯定是比较耗时的,这种情况下,我们要考虑将这一个文件合理的拆分成多个,这样就可以用多个map任务去完成。

set mapred.reduce.tasks=10;
  create table a_1 as
  select * from a
  distribute by rand(123);

  这样会将a表的记录,随机的分散到包含10个文件的a_1表中,再用a_1代替上面sql中的a表,则会用10个map任务去完成。每个map任务处理大于12M(几百万记录)的数据,效率肯定会好很多。

2、reduce数优化

  Reduce的个数对整个作业的运行性能有很大影响。如果Reduce设置的过大,那么将会产生很多小文件,对NameNode会产生一定的影响,而且整个作业的运行时间未必会减少;如果Reduce设置的过小,那么单个Reduce处理的数据将会加大,很可能会引起OOM异常。

  如果设置了mapred.reduce.tasks/mapreduce.job.reduces参数,那么Hive会直接使用它的值作为Reduce的个数;如果mapred.reduce.tasks/mapreduce.job.reduces的值没有设置(也就是-1),那么Hive会根据输入文件的大小估算出Reduce的个数。根据输入文件估算Reduce的个数可能未必很准确,因为Reduce的输入是Map的输出,而Map的输出可能会比输入要小,所以最准确的数根据Map的输出估算Reduce的个数。

自己如何确定reduce数:

  reduce个数的设定极大影响任务执行效率,不指定reduce个数的情况下,Hive会猜测确定一个reduce个数,基于以下两个设定:

hive.exec.reducers.bytes.per.reducer(每个reduce任务处理的数据量,默认为1000^3=1G)
hive.exec.reducers.max(每个任务最大的reduce数,默认为1009)

  计算reducer数的公式很简单N=min(参数2,总输入数据量/参数1)。即,如果reduce的输入(map的输出)总大小不超过1G,那么只会有一个reduce任务。

如:select pt,count(1) from popt_tbaccountcopy_mes where pt = ‘2012-07-04’ group by pt;
  总大小为9G多
  因此这句有10个reduce

  1. 调整reduce个数方法一

(1)每个Reduce处理的数据量默认是256MB
hive.exec.reducers.bytes.per.reducer=256123456
(2)每个任务最大的reduce数,默认为1009
hive.exec.reducers.max=1009

  1. 调整reduce个数方法二

在hadoop的mapred-default.xml文件中修改
设置每个job的Reduce个数
set mapreduce.job.reduces = 15;

  1. reduce个数并不是越多越好

   1)过多的启动和初始化reduce也会消耗时间和资源;

   2)另外,有多少个reduce,就会有多少个输出文件,如果生成了很多个小文件,那么如果这些小文件作为下一个任务的输入,则也会出现小文件过多的问题;

  注意:在设置reduce个数的时候也需要考虑这两个原则:使大数据量利用合适的reduce数;使单个reduce任务处理合适的数据量。

3、Fetch抓取(Hive可以避免进行MapReduce)

  Hive中对某些情况的查询可以不必使用MapReduce计算。例如:SELECT * FROM employees;在这种情况下,Hive可以简单地读取employee对应的存储目录下的文件,然后输出查询结果到控制台。
  在hive-default.xml.template文件中hive.fetch.task.conversion默认是more,老版本hive默认是minimal,该属性修改为more以后,在全局查找、字段查找、limit查找等都不走MapReduce。

![在这里插入图片描述]()

案例实操

  • 1)把hive.fetch.task.conversion设置成none,然后执行查询语句,都会执行mapreduce程序。
hive (default)> set hive.fetch.task.conversion=none;
hive (default)> select * from score;
hive (default)> select s_score from score;
hive (default)> select s_score from score limit 3;




  • 2)把hive.fetch.task.conversion设置成more,然后执行查询语句,如下查询方式都不会执行mapreduce程序。
hive (default)> set hive.fetch.task.conversion=more;
hive (default)> select * from score;
hive (default)> select s_score from score;
hive (default)> select s_score from score limit 3;

4 、模式选择

  大多数的Hadoop job是需要Hadoop提供的完整的可扩展性来处理大数据集的。不过,有时Hive的输入数据量是非常小的。在这种情况下,为查询触发执行任务时消耗可能会比实际job的执行时间要多的多。对于大多数这种情况,Hive可以通过本地模式在单台机器上处理所有的任务。对于小数据集,执行时间可以明显被缩短。
  用户可以通过设置hive.exec.mode.local.auto的值为true,来让Hive在适当的时候自动启动这个优化。本地模式涉及到三个参数:


参数名

默认值

备注

hive.exec.mode.local.auto

false

让hive决定是否在本地模式自动运行

hive.exec.mode.local.auto.input.files.max

4

不启动本地模式的task最大个数

hive.exec.mode.local.auto.inputbytes.max

128M

不启动本地模式的最大输入文件大小


set hive.exec.mode.local.auto=true;  //开启本地mr
//设置local mr的最大输入数据量,当输入数据量小于这个值时采用local  mr的方式,默认为134217728,即128M
set hive.exec.mode.local.auto.inputbytes.max=50000000;
//设置local mr的最大输入文件个数,当输入文件个数小于这个值时采用local mr的方式,默认为4
set hive.exec.mode.local.auto.input.files.max=10;

案例实操:

1)开启本地模式,并执行查询语句
hive (default)> set hive.exec.mode.local.auto=true;
hive (default)> select * from score cluster by s_id;
18 rows selected (1.568 seconds)
2)关闭本地模式,并执行查询语句
hive (default)> set hive.exec.mode.local.auto=false;
hive (default)> select * from score cluster by s_id;
18 rows selected (11.865 seconds)

  Hive会将一个查询转化成一个或多个阶段。这样的阶段可以是MapReduce阶段、抽样阶段、合并阶段、limit阶段。默认情况下,Hive一次只会执行一个阶段,由于job包含多个阶段,而这些阶段并非完全相互依赖,即:这些阶段可以并行执行,可以缩短整个job的执行时间。设置参数,set hive.exec.parallel=true或者通过配置文件来完成。

  Hive提供一个严格模式,可以防止用户执行那些可能产生意想不到的影响查询,通过设置hive.mapred.mode值为strict来完成。默认是非严格模式nonstrict。

  1)对于分区表,除非where语句中含有分区字段过滤条件来限制范围,否则不允许执行。换句话说,就是用户不允许扫描所有分区。进行这个限制的原因是,通常分区表都拥有非常大的数据集,而且数据增加迅速。没有进行分区限制的查询可能会消耗令人不可接受的巨大资源来处理这个表。

  2)对于使用了order by语句的查询,要求必须使用limit语句。因为order by为了执行排序过程会将所有的结果数据分发到同一个Reducer中进行处理,强制要求用户增加这个LIMIT语句可以防止Reducer额外执行很长一段时间。

  3)限制笛卡尔积的查询。对关系型数据库非常了解的用户可能期望在执行JOIN查询的时候不使用ON语句而是使用where语句,这样关系数据库的执行优化器就可以高效地将WHERE语句转化成那个ON语句。不幸的是,Hive并不会执行这种优化,因此,如果表足够大,那么这个查询就会出现不可控的情况。

5、JVM重用

  JVM重用是Hadoop调优参数的内容,其对Hive的性能具有非常大的影响,特别是对于很难避免小文件的场景或task特别多的场景,这类场景大多数执行时间都很短。

  JVM重用可以使得JVM实例在同一个job中重新使用N次。N的值可以在Hadoop的mapred-site.xml文件中进行配置。通常在10-20之间,具体多少需要根据具体业务场景测试得出。

<property>
  <name>mapreduce.job.jvm.numtasks</name>
  <value>10</value>
  <description>How many tasks to run per jvm. If set to -1, there is
  no limit.
  </description>
</property>

  我们也可以在hive当中通过

set  mapred.job.reuse.jvm.num.tasks=10;

  这个设置来设置我们的jvm重用。

缺点

  开启JVM重用将一直占用使用到的task插槽,直到任务完成后才能释放。如果某个“不平衡的”job中有某几个Reduce task执行的时间要比其他Reduce task消耗的时间多的多的话,那么保留的插槽就会一直空闲着却无法被其他的job使用,直到所有的task都结束了才会释放。

6、推测执行

  在分布式集群环境下,因为程序Bug(包括Hadoop本身的bug),负载不均衡或者资源分布不均等原因,会造成同一个作业的多个任务之间运行速度不一致,有些任务的运行速度可能明显慢于其他任务(比如一个作业的某个任务进度只有50%,而其他所有任务已经运行完毕),则这些任务会拖慢作业的整体执行进度。为了避免这种情况发生,Hadoop采用了推测执行(Speculative Execution)机制,它根据一定的法则推测出“拖后腿”的任务,并为这样的任务启动一个备份任务,让该任务与原始任务同时处理同一份数据,并最终选用最先成功运行完成任务的计算结果作为最终结果。
hadoop的推测执行功能由mapred-site.xml文件中的2个参数决定:

<property>
	<name> mapred.map.tasks.speculative.execution </name>
	<value>true</value>
</property>
<property>
	<name> mapred.reduce.tasks.speculative.execution</name>
	<value>true</value>
</property>

hive本身也有控制推测执行的参数,可以在hive-site.xml文件中配置:

<property>
	<name>hive.mapred.reduce.tasks.speculative.execution </name>
	<value>true</value>
</property>

hive中推测执行参数默认值如下:

hive (default)> set mapred.map.tasks.speculative.execution;
mapred.map.tasks.speculative.execution=true
hive (default)> set mapred.reduce.tasks.speculative.execution;
mapred.reduce.tasks.speculative.execution=true
hive (default)> set hive.mapred.reduce.tasks.speculative.execution;
hive.mapred.reduce.tasks.speculative.execution=true

  关于调优这些推测执行变量,还很难给一个具体的建议。如果用户对于运行时的偏差非常敏感的话,那么可以将这些功能关闭掉。如果用户因为输入数据量很大而需要执行长时间的map task或者reduce task的话,那么启动推测执行造成的浪费是非常巨大大。

7、并行执行

  Hive会将一个查询转化成一个或者多个阶段。例如:MapReduce阶段、抽样阶段、合并阶段、limit阶段。或者Hive执行过程中可能需要的其他阶段。默认情况下,Hive一次只会执行一个阶段。不过,某个特定的job可能包含众多的阶段,而这些阶段可能并非完全互相依赖的,也就是说有些阶段是可以并行执行的,这样可能使得整个job的执行时间缩短。不过,如果有更多的阶段可以并行执行,那么job可能就越快完成。

// 开启任务并行执行
 set hive.exec.parallel=true;
// 同一个sql允许并行任务的最大线程数 
set hive.exec.parallel.thread.number=8;

8、合并小文件

  小文件的产生有三个地方,map输入,map输出,reduce输出,小文件过多也会影响hive的分析效率:

设置map输入的小文件合并

set mapred.max.split.size=256000000;
//一个节点上split的至少的大小(这个值决定了多个DataNode上的文件是否需要合并)
set mapred.min.split.size.per.node=100000000;
//一个交换机下split的至少的大小(这个值决定了多个交换机上的文件是否需要合并)  
set mapred.min.split.size.per.rack=100000000;
//执行Map前进行小文件合并
set hive.input.format=org.apache.hadoop.hive.ql.io.CombineHiveInputFormat;

设置map输出和reduce输出进行合并的相关参数:

//设置map端输出进行合并,默认为true
set hive.merge.mapfiles = true
//设置reduce端输出进行合并,默认为false
set hive.merge.mapredfiles = true
//设置合并文件的大小
set hive.merge.size.per.task = 256*1000*1000
//当输出文件的平均大小小于该值时,启动一个独立的MapReduce任务进行文件merge。
set hive.merge.smallfiles.avgsize=16000000

二、hive压缩存储优化

1、压缩优化

  hive最终是转为MapReduce程序来执行的,而MapReduce的性能瓶颈在于网络IO和磁盘IO,要解决性能瓶颈,最主要的是减少数据量,对数据进行压缩是个好的方式。但是在压缩和解压过程中会增加CPU的开销。不过往往性能瓶颈不在于CPU,所以针对IO密集型的jobs(非计算密集型)可以使用压缩的方式提高性能

hive求开始结束中间年月 hive取当前日期的周一_Hive

  各个压缩方式所对应的 Class 类:

hive求开始结束中间年月 hive取当前日期的周一_hive求开始结束中间年月_02

压缩率
压缩解压缩速度
是否支持spllit

Job输出文件按照block以Gzip的方式进行压缩:

set mapreduce.output.fileoutputformat.compress=true // 默认值是 false
set mapreduce.output.fileoutputformat.compress.type=BLOCK // 默认值是 Record
set mapreduce.output.fileoutputformat.compress.codec=org.apache.hadoop.io.compress.GzipCodec
// 默认值是 org.apache.hadoop.io.compress.DefaultCodec

Map输出结果也以Gzip进行压缩:

set mapred.map.output.compress=true
set mapreduce.map.output.compress.codec=org.apache.hadoop.io.compress.GzipCodec // 默认值是 org.apache.hadoop.io.compress.DefaultCodec

对Hive输出结果和中间都进行压缩:

set hive.exec.compress.output=true // 默认值是 false,不压缩
set hive.exec.compress.intermediate=true // 默认值是 false,为 true 时 MR 设置的压缩才启用

2、存储优化

  可以使用列裁剪,分区裁剪,orc,parquet等这些列式存储格式,因为列式存储的表,每一列的数据在物理上是存储在一起的,Hive查询时会只遍历需要列数据,大大减少处理的数据量。

  1. TextFile

  Hive数据表的默认格式,存储方式:行存储。
  可以使用Gzip压缩算法,但压缩后的文件不支持split 在反序列化过程中,必须逐个字符判断是不是分隔符和行结束符,因此反序列化开销会比SequenceFile高几十倍。

  1. Sequence Files

  Hadoop中有些原生压缩文件的缺点之一就是不支持分割。支持分割的文件可以并行的有多个mapper程序处理大数据文件,大多数文件不支持可分割是因为这些文件只能从头开始读。Sequence File是可分割的文件格式,支持Hadoop的block级压缩。 Hadoop API提供的一种二进制文件,以key-value的形式序列化到文件中。存储方式:行存储。 sequencefile支持三种压缩选择:NONE,RECORD,BLOCK。Record压缩率低,RECORD是默认选项,通常BLOCK会带来较RECORD更好的压缩性能。 优势是文件和hadoop api中的MapFile是相互兼容的。

  1. RCFile

  存储方式:数据按行分块,每块按列存储。结合了行存储和列存储的优点:
  首先,RCFile 保证同一行的数据位于同一节点,因此元组重构的开销很低 其次,像列存储一样,RCFile 能够利用列维度的数据压缩,并且能跳过不必要的列读取 数据追加:RCFile不支持任意方式的数据写操作,仅提供一种追加接口,这是因为底层的 HDFS当前仅仅支持数据追加写文件尾部。行组大小:行组变大有助于提高数据压缩的效率,但是可能会损害数据的读取性能,因为这样增加了 Lazy 解压性能的消耗。而且行组变大会占用更多的内存,这会影响并发执行的其他MR作业。

  1. ORCFile

  存储方式:数据按行分块,每块按照列存储
  压缩快,快速列存取。效率比rcfile高,是rcfile的改良版本。

  1. Parquet

  Parquet也是一种列式存储,同时具有很好的压缩性能;同时可以减少大量的表扫描和反序列化的时间。

  1. 自定义格式

  可以自定义文件格式,用户可通过实现InputFormat和OutputFormat来自定义输入输出格式。

结论:一般选择ORCFile/parquet + snappy 的方式

create table tablename (
 xxx,string
 xxx, bigint
)
ROW FORMAT DELTMITED FIELDS TERMINATED BY '\t'
STORED AS orc tblproperties("orc.compress" = "SNAPPY")

三、hive表设计优化

1、内部表 & 外部表

区别:   (1)创建表时指定external关键字,就是外部表,不指定external就是内部表(也称管理表)。

  (2)内部表删除后把元数据和数据都删除了,外部表删除后只是删除了元数据,不会删除hdfs上的数据文件。

  (3)外部表创建表时通过location指定存放表数据的hdfs上的路径,而内部表是默认存放在hive-site.xml中设置的路径。

2、分区

分区主要用于提高性能

  1.分区列的值将表划分为segments(文件夹)

  2.查询时使用“分区”列和常规列类似

  3.查询时Hive自动过滤掉不用于提高性能的分区

建表时通过PARTITIONED BY定义分区

CREATE TABLE employee_partitioned(
    name string,
    work_place ARRAY<string>,
    sex_age STRUCT<sex:string,age:int>,
    skills_score MAP<string,int>,
    depart_title MAP<STRING,ARRAY<STRING>> )
PARTITIONED BY (year INT, month INT)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY '|'
COLLECTION ITEMS TERMINATED BY ','
MAP KEYS TERMINATED BY ':';

ALTER TABLE的方式添加静态分区,ADD添加分区, DROP删除分区

ALTER TABLE employee_partitioned ADD
PARTITION (year=2019,month=3) PARTITION (year=2019,month=4);
ALTER TABLE employee_partitioned DROP PARTITION (year=2019, month=4);

使用动态分区需设定属性

set hive.exec.dynamic.partition=true;
set hive.exec.dynamic.partition.mode=nonstrict;

动态分区设置方法

insert into table employee_partitioned partition(year, month)
select name,array('Toronto') as work_place,
named_struct("sex","male","age",30) as sex_age,
map("python",90) as skills_score,
map("r&d", array('developer')) as depart_title,
year(start_date) as year,month(start_date) as month
from employee_hr eh ;

  默认情况下,用户必须指定至少一个静态分区列。这是为了避免意外地覆盖分区。
  使用动态分区需禁用此限制,可以将分区模式从默认的严格模式设置为非严格模式

3、分桶

  桶是更细粒度的划分, 相同的数据分到一个桶里面,减少数据访问的量,对每一个表或者分区,hive可以进行进一步的分桶。
  1.分桶对应于HDFS中的文件
  2.更高的查询处理效率
  3.使抽样(sampling)更高效
  4.根据“桶列”的哈希函数将数据进行分桶
  5.分桶只有动态分桶

SET hive.enforce.bucketing = true;

定义分桶

CLUSTERED BY (employee_id) INTO 2 BUCKETS  //分桶的列employee_id是表中已有的列

  1.分桶数最好是2的n次方
  2.必须使用INSERT方式加载数据到设置分桶的表,才会生效
  3.与分区不同,分桶列名出现在列定义中

四、SQL优化

1、小表join大表

  新的版本当中已经没有区别了,旧的版本当中需要使用小表。   在使用写有join操作的查询语句时有一条原则:应该将条目少的表/子查询放在join操作符的左边。原因是在Join操作的Reduce阶段,位于join操作符左边的表的内容会被加载进内存,将条目少的表放在左边,可以有效减少发生OOM错误的几率;再进一步,可以使用Group让小的维度表(1000条以下的记录条数)先进内存。在map端完成reduce。
  多个表关联时,最好分拆成小段,避免大sql(无法控制中间Job)。

2、大表join大表

有时join超时是因为某些key对应的数据太多,而相同key对应的数据都会发送到相同的reducer上,从而导致内存不够。此时我们应该仔细分析这些异常的key,很多情况下,这些key对应的数据是异常数据,我们需要在SQL语句中进行过滤。例如key对应的字段为空。对比如下:

  • 不过滤
INSERT OVERWRITE TABLE jointable
SELECT a.* FROM nullidtable a JOIN ori b ON a.id = b.id;
结果:
No rows affected (152.135 seconds)
  • 过滤
INSERT OVERWRITE TABLE jointable
SELECT a.* FROM (SELECT * FROM nullidtable WHERE id IS NOT NULL ) a JOIN ori b ON a.id = b.id;
结果:
No rows affected (141.585 seconds)

  问题:日志中常会出现信息丢失,比如每日约为20亿的全网日志,其中的user_id为主键,在日志收集过程中会丢失,出现主键为null的情况,如果取其中的user_id和bmw_users关联,就会碰到数据倾斜的问题。原因是Hive中,主键为null值的项会被当做相同的Key而分配进同一个计算Map。

解决方法1:user_id为空的不参与关联,子查询过滤null

SELECT * FROM log a
JOIN bmw_users b ON a.user_id IS NOT NULL AND a.user_id=b.user_id
UNION ALL SELECT * FROM log a WHERE a.user_id IS NULL

解决方法2:函数过滤null

SELECT * FROM log a LEFT OUTER
JOIN bmw_users b ON
CASE WHEN a.user_id IS NULL THEN CONCAT('dp_hive', RAND()) ELSE a.user_id END = b.user_id;

  调优结果:原先由于数据倾斜导致运行时长超过1小时,解决方法1运行每日平均时长25分钟,解决方法2运行的每日平均时长在20分钟左右。优化效果很明显。

  解决方法2比解决方法1效果更好,不但IO少了,而且作业数也少了。解决方法1中log读取两次,job数为2。解决方法2中job数是1。这个优化适合无效id(比如-99,‘’,null等)产生的倾斜问题。把空值的key变成一个字符串加上随机数,就能把倾斜的数据分到不同的Reduce上,从而解决数据倾斜问题。因为空值不参与关联,即使分到不同的Reduce上,也不会影响最终的结果。附上Hadoop通用关联的实现方法是:关联通过二次排序实现的,关联的列为partition key,关联的列和表的tag组成排序的group key,根据partition key分配Reduce。同一Reduce内根据group key排序。

3、mapjoin

  如果不指定MapJoin或者不符合MapJoin的条件,那么Hive解析器会将Join操作转换成Common Join,即:在Reduce阶段完成join。容易发生数据倾斜。可以用MapJoin把小表全部加载到内存在map端进行join,避免reducer处理。

  • 开启MapJoin参数设置:
(1)设置自动选择Mapjoin
set hive.auto.convert.join = true; 默认为true
(2)大表小表的阈值设置(默认25M以下认为是小表):
set hive.mapjoin.smalltable.filesize=25123456;

4、group by

  默认情况下,Map阶段同一Key数据分发给一个reduce,当一个key数据过大时就倾斜了。
  并不是所有的聚合操作都需要在Reduce端完成,很多聚合操作都可以先在Map端进行部分聚合,最后在Reduce端得出最终结果。

  • 开启Map端聚合参数设置
(1)是否在Map端进行聚合,默认为True
   set hive.map.aggr = true;
(2)在Map端进行聚合操作的条目数目
   set hive.groupby.mapaggr.checkinterval = 100000;
(3)有数据倾斜的时候进行负载均衡(默认是false)
   set hive.groupby.skewindata = true;

  当选项设定为 true,生成的查询计划会有两个MapReduce Job。
  第一个MapReduce Job中,Map的输出结果会随机分布到Reduce中,每个Reduce做部分聚合操作,并输出结果,这样处理的结果是相同的Group By Key有可能被分发到不同的Reduce中,从而达到负载均衡的目的;
  第二个MapReduce Job再根据预处理的数据结果按照Group By Key分布到Reduce中(这个过程可以保证相同的Group By Key被分布到同一个Reduce中),最后完成最终的聚合操作。

5、笛卡尔积

  尽量避免笛卡尔积,join的时候不加on条件,或者无效的on条件,Hive只能使用1个reducer来完成笛卡尔积。
  当 Hive 设定为严格模式(hive.mapred.mode=strict)时,不允许在 HQL 语句中出现笛卡尔积, 这实际说明了 Hive 对笛卡尔积支持较弱。因为找不到 Join key,Hive 只能使用 1 个 reducer 来完成笛卡尔积。
  当然也可以使用 limit 的办法来减少某个表参与 join 的数据量,但对于需要笛卡尔积语义的需求来说,经常是一个大表和一个小表的 Join 操作,结果仍然很大(以至于无法用单机处理),这时 MapJoin才是最好的解决办法。MapJoin,顾名思义,会在 Map 端完成 Join 操作。 这需要将 Join 操作的一个或多个表完全读入内存。
  MapJoin 在子查询中可能出现未知 BUG。在大表和小表做笛卡尔积时,规避笛卡尔积的方法是:给 Join 添加一个 Join key,原理很简单:将小表扩充一列 join key,并将小表的条目复制数倍,join key 各不相同;将大表扩充一列 join key 为随机数。精髓就在于复制几倍,最后就有几个 reduce 来做,而且大表的数据是前面小表扩张 key 值 范围里面随机出来的,所以复制了几倍 n,就相当于这个随机范围就有多大 n,那么相应的, 大表的数据就被随机的分为了 n 份。并且最后处理所用的 reduce 数量也是 n,而且也不会出现数据倾斜。

6、count(distinct)去重统计

  数据量小的时候无所谓,数据量大的情况下,由于COUNT DISTINCT操作需要用一个Reduce Task来完成,这一个Reduce需要处理的数据量太大,容易产生倾斜问题,就会导致整个Job很难完成。一般COUNT DISTINCT使用先GROUP BY再COUNT的方式替换。

7、行列过滤

1、列处理

  在 SELECT 中,只拿需要的列,如果有,尽量使用分区过滤,少用 SELECT *。
2、行处理

  在分区剪裁中,当使用外关联时,过滤条件最好用在子查询里。

(1)先关联两张表,再用 where 条件过滤 (不推荐)

select o.id from bigtable b join ori o on o.id = b.id where o.id <= 10;

(2)优化后,通过子查询后,再关联表 (推荐)

select b.id from bigtable b join (select id from ori where id <= 10 ) o on b.id = o.id;

8、排序选择

cluster by:对同一字段分桶并排序,不能和 sort by 连用。

distribute by + sort by:分桶,保证同一字段值只存在一个结果文件当中,结合 sort by 保证 每个 reduceTask 结果有序。

sort by:单机排序,单个 reduce 结果有序。

order by:全局排序,缺陷是只能使用一个 reduce。

9、查看sql的执行计划(Explain)

1)基本语法

Explain [extended | dependency | authorization] query

2)案例实操

(1)查看下面这条语句的执行计划

hive (default)> explain select * from emp;

(2)查看详细执行计划

hive (default)> explain extended select * from emp;

五、数据倾斜

  表现:任务进度长时间维持在99%(或100%),查看任务监控页面,发现只有少量(1个或几个)reduce子任务未完成。因为其处理的数据量和其他reduce差异过大。

  原因:某个reduce的数据输入量远远大于其他reduce数据的输入量

1)、key分布不均匀

2)、业务数据本身的特性

3)、建表时考虑不周

4)、某些SQL语句本身就有数据倾斜


关键词

情形

后果

join

其中一个表较小,但是key集中

分发到某一个或几个Reduce上的数据远高于平均值

join

大表与大表,但是分桶的判断字段0值或空值过多

这些空值都由一个reduce处理,非常慢

group by

group by 维度过小,某值的数量过多

处理某值的reduce非常耗时

count distinct

某特殊值过多

处理此特殊值reduce耗时


解决方案:

(1)参数调节

set hive.map.aggr=true
set hive.groupby.skewindata=true

(2) 熟悉数据的分布,优化sql的逻辑,找出数据倾斜的原因。