Hive——Hive/HiveSQL性能优化

文章目录

  • Partition分区
    • 1. 静态分区Static Partition
    • 2. 动态分区Dynamic Partition
  • Bucket分桶
  • 使用Spark作为执行引擎
  • 使用压缩
  • 使用ORC格式
  • Join优化
    • 1. STREAMTABLE
    • 2. 前置过滤条件
    • 3. Multi-way Join
    • 4. Map Join(Broadcast Join/Broadcast-Hash Join)
    • 5. Skew Join
  • 基于代价的优化
  • 参考

我们知道Hive是一个构建在MapReduce之上并提供了SQL语法的查询分析引擎。虽然Hive可以处理巨量的数据,但是不同的优化手段会在处理时间上产生很大的差异。

在Hive中,可以从以下几个方面进行优化:

  • 分区partition
  • 分桶bucket
  • 使用Spark作为执行引擎
  • 使用压缩
  • 使用ORC格式
  • join优化
  • 基于CBO的优化

Partition分区

在对表基于分区列进行分区之后,写入表的数据会存放在不同的目录下,但是分区列的数据是不存储在目录下的文件中的。当通过分区列进行查询表数据的时候,只会扫描指定分区的数据,其他的分区的数据是不会被查询的,这就减少了磁盘I/O时间,从而提升了查询性能。

分区表的创建:

create table table_name (
  id                int,
  dtDontQuery       string,
  name              string
)
partitioned by (date string)

注意:分区列是定义在partitioned by后面的,并非字段列表中。

分区又分为静态分区和动态分区两种。

1. 静态分区Static Partition

静态分区是指我们提前就知道要插入到表中数据的分区,比如下面语句中的分区列country:

FROM page_view_stg pvs
INSERT OVERWRITE TABLE page_view PARTITION(dt='2008-06-08', country='US')
       SELECT pvs.viewTime, pvs.userid, pvs.page_url, pvs.referrer_url, null, null, pvs.ip WHERE pvs.country = 'US'

静态分区是非常不方便的,因为要执行多个语句将不同分区的数据插入到对应的分区中。

2. 动态分区Dynamic Partition

动态分区不用指定分区的值,而是在插入数据的过程中,自动创建分区。比如下面语句中的分区列country:

FROM page_view_stg pvs
INSERT OVERWRITE TABLE page_view PARTITION(dt='2008-06-08', country)
       SELECT pvs.viewTime, pvs.userid, pvs.page_url, pvs.referrer_url, null, null, pvs.ip, pvs.country

Hive中启用动态分区(Hive默认值就是true):

set hive.exec.dynamic.partition=true

另外,动态分区也有两种模式:

  • Strict模式:分区列中至少有一个是静态分区列。
  • Non-strict模式:分区列可以全部为动态分区列。

切换动态分区模式(Hive中动态分区模式默认为strict):

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

Bucket分桶

Hive表中,每个分区对应一个目录,而每个分桶对应一个文件。

创建分桶表语句:

create table page_view(
    viewtime int,
    userid bigint,
    page_url string,
    referrer_url string,
    ip string comment 'ip address of the user'
) comment 'this is the page view table' 
partitioned by(dt string, country string) 
clustered by(userid) sorted by(viewtime) into 32 buckets 

Hive是通过在某一列上应用hash函数将数据分为指定数量的文件,bucket的个数由公式hash_function(bucketing_column) mod num_buckets,其中的哈希函数主要依赖于分桶列的数据类型。对于int类型,hash_int(i) == i,比如说int类型的user_id,以0结尾的user_id会划分到第1个bucket,以1结尾的user_id会划分到第2个bucket。 分桶也是为了提高查询效率。

使用Spark作为执行引擎

默认情况下,Hive底层是基于MapReduce,而MapReduce计算过程是基于磁盘的,这也就是预示着任务的执行会比较耗时。那么,这时我们就可以替换底层执行引擎,用基于内存计算的Spark。

查询时指定执行引擎(Hive中默认执行引擎为mr,即hive.execution.engine=mr):

set hive.execution.engine=spark

或者在hive-site.xml中配置默认执行引擎。

使用压缩

我们知道Hive中的查询很多会涉及到大量的磁盘I/O和网络I/O,由此想到,如果我们能减少查询数据大小的话,就可以提升查询效率。那如何减少数据的大小呢?答案就是对数据进行压缩。此外,压缩也能减少磁盘的占用。

Hive输出启用压缩(默认是未启用压缩的):

hive.exec.compress.output=true

但是,也不能一味的追求压缩率,因为高压缩率会导致解压时消耗更多的CPU,因此我们要在压缩和解压缩之间取得平衡。常用的压缩格式有snappy、bzip2、gzip等,Spark中默认的压缩格式就是snappy。

使用ORC格式

选择合适的文件格式可以在很大程度上提升任务执行的效率。Hive支持很多的文件格式:Text、Parquet、ORC、Avro、Sequence等,甚至还可以自定义文件格式。

当我们读取Text格式表中的某一列的时候,必须要把一整行数据都读出来。而如果我们将表设置为列式存储格式(比如Parquet和ORC),我只需查询所需要的列,不需要的列是不会扫描的。

ORC格式在Hive中以下的优化点:

  • 列式存储可以实现更高的压缩率(Hive在使用ORC格式时默认的压缩格式为ZLIB)
  • ORC可以使用存储在文件中的轻量级索引,跳过不必要的记录扫描
  • 如果Block中的行记录与查询无关,则可以跳过这些行,不进行解压缩

实际上,ORC存储格式既是面向行的,又是面向列的。一个ORC格式的文件一系列的stripes组成的,每个stripe中,列是被分别压缩的,每个stripe由以下3部分组成:

  1. Stripe footer:存放文件级和stripe级的统计信息,以确定是否需要读取文件的剩余部分。
  2. Row data:默认包含10000行数据。
  3. index data:包含每一列的最大、最小值,以及每一列所处的行的位置信息。

使用ORC格式可以用到以下优化手段:

  1. 查询下推

  2. 布隆过滤器

  3. ORC压缩

  4. 向量化执行
    使用向量化的查询执行,可以在很大程度上优化scan、filter、aggregation、join等操作。标准的查询执行一次只处理一行,而且会 走过很长的代码调用和元数据解释。而向量化查询执行每次可以处理1024行,每一行会被保存为一个向量。对于向量来说,简单的算术和比较运行可以很快的完成。

    执行树被加快是因为,在相同数据类型的数据块中,编译器可在紧凑循环中生成代码来执行相同的函数,而不必遍历独立函数调用所需的长代码路径。这种类似SIMD的行为带来了很多好处:执行的指令更少、缓存行为更好、改进的管道、更有利的TLB行为,等等。

    可通过以下配置使用向量化查询执行:

    set hive.vectorized.execution.enabled = true; //默认为true
    set hive.vectorized.execution.reduce.enabled=true; //默认为false
    set hive.vectorized.execution.reduce.groupby.enabled=true; //默认为true
    

Join优化

在了解Join优化之前,我们首先得知道join查询是如何转换成MapReduce进行执行的:

  1. Mapper端会先基于join key并行地对表数据进行排序
  2. 排序后传递给Reducer端,具有相同key的tuple会被传递给同一个Reducer,但是一个Reducer可能会处理多个key对应的tuple。tuple中也会包含参与join的表的id,标识这一条数据是属于哪个表的。

1. STREAMTABLE

join的每个map/reduce阶段,join序列中的最后一个表会默认作为流表,其他的表会缓存起来。因此,一般会将最大表放到一系列join操作的最后一个表,这样可以减少内存占用。或者我们也可以显示的指定流表:

SELECT /*+ STREAMTABLE(a) */ a.val, b.val, c.val FROM a JOIN b ON (a.key = b.key1) JOIN c ON (c.key = b.key1)

2. 前置过滤条件

另外一个当我们在使用join的时候要注意的是,首先进行join,然后对join结果再执行where条件进行过滤,例如下面的查询语句:

SELECT a.col, b.col FROM a LEFT OUTER JOIN b ON (a.id = b.id) WHERE a.datetime = '2020–11–11' AND b.datetime = '2020–11–11'

针对上面的问题,我们可以将过滤条件提前执行,以减少不必要数据的查询:

SELECT a.col, b.col FROM a LEFT OUTER JOIN b ON (a.id = b.id and a.datetime = '2020–11–11' AND b.datetime = '2020–11–11')

由于join操作会涉及到排序和shuffle操作,而这些操作往往产生大量的IO操作,造成任务运行耗时较长。针对这些问题,Hive中使用了以下join优化算法:

  • Multi-way Join
  • Map Join
  • Skew Join

3. Multi-way Join

如果多个join操作共享同一个join key,那么所有这些join操作可以由一个reduce task来执行,减少了reduce的数量,就比如下面的SQL:

select t1.x, t2.col2, t3.col3, t4.col4 from t1 
left join t2 on t1.x = t2.col2
left join t3 on t1.x = t3.col3
left join t4 on t1.x = t4.col4

4. Map Join(Broadcast Join/Broadcast-Hash Join)

这种join优化算法非常适合类似星型模型中小维表和大事实表进行join的场景,它会将小表加载到所有mapper端的内存中,大表作为流表,在map端即可完成join操作,不会产生昂贵的shuffle的操作。

通过以下配置,Hive会自动优化哪些适合Map端join的join场景:

set hive.auto.convert.join=true; //默认为true
set hive.auto.convert.join.noconditionaltask = true; //默认为true
set hive.auto.convert.join.noconditionaltask.size = 2147483648; //默认为2147483648

或者我们也可以显示地指定:

SELECT /*+ MAPJOIN(b) */ a.key, a.value FROM a JOIN b ON a.key = b.key

5. Skew Join

对于出现数据倾斜的情况,Hive会自动进行优化,拆分成多个子任务,最后再将子任务的结果进行合并。相关的配置:

set hive.optimize.skewjoin = true; //默认为false
set hive.skewjoin.key=100000; //默认为100000

基于代价的优化

Hive使用硬编码查询计划来执行查询,直到出现了基于代价的优化模型(cost-based optimizer, CBO),CBO通过搜集元数据信息来优化查询计划,它提供了两种类型的优化:逻辑优化和物理优化。

CBO的开启:

hive.cbo.enable=true; //默认为false

参考

  1. https://towardsdatascience.com/apache-hive-optimization-techniques-1-ce55331dbf5e
  2. https://towardsdatascience.com/apache-hive-optimization-techniques-2-e60b6200eeca
  3. https://cwiki.apache.org/confluence/display/Hive/Cost-based+optimization+in+Hive
  4. https://cwiki.apache.org/confluence/display/Hive/Tutorial#Tutorial-Dynamic-PartitionInsert
  5. https://cwiki.apache.org/confluence/display/Hive/LanguageManual+Joins
  6. https://orc.apache.org/docs/indexes.html

你可能感兴趣的:(Hive,Hive)