(转)「大数据」Hive 分区和分桶的区别及示例讲解

一、概述

在大数据处理过程中,Hive是一种非常常用的数据仓库工具。Hive分区和分桶是优化Hive性能的两种方式,它们的区别如下:

1)分区概述

Hive分区是把数据按照某个属性分成不同的数据子集。

  • 在Hive中,数据被存储在HDFS中,每个分区实际上对应HDFS下的一个文件夹,这个文件夹中保存了这个分区的数据。
  • 因此,在Hive中使用分区,实际上是将数据按照某个属性值进行划分,然后将相同属性值的数据存储在同一个文件夹中。Hive分区的效率提升主要是因为,当进行查询操作时,只需读取与查询相关的数据分区,避免了全表扫描,节约了查询时间

Hive分区的主要作用是:

  • 提高查询效率: 使用分区对数据进行访问时,系统只需要读取和此次查询相关的分区,避免了全表扫描,从而显著提高查询效率。
  • 降低存储成本: 分区可以更加方便的删除过期数据,减少不必要的存储。

2)分桶概述

Hive分桶是将数据划分为若干个存储文件,并规定存储文件的数量。

  • Hive分桶的实现原理是将数据按照某个字段值分成若干桶,并将相同字段值的数据放到同一个桶中。在存储数据时,桶内的数据会被写入到对应数量的文件中,最终形成多个文件。
  • Hive分桶主要是为了提高分布式查询的效率。它能够通过将数据划分为若干数据块来将大量数据分发到多个节点,使得数据均衡分布到多个机器上处理。这样分发到不同节点的数据可以在本地进行处理,避免了数据的传输和网络带宽的浪费,同时提高了查询效率。

分桶的主要作用是:

  • 数据聚合: 分桶可以使得数据被分成较小的存储单元,提高了数据统计和聚合的效率。
  • 均衡负载: 数据经过分桶后更容易实现均衡负载,数据可以分发到多个节点中,提高了查询效率。

综上所述,分区和分桶的区别在于其提供的性能优化方向不同。分区适用于对于数据常常进行的聚合查询数据分析,而分桶适用于对于数据的均衡负载、高效聚合等方面的性能优化。当数据量较大、查询效率比较低时,使用分区和分桶可以有效优化性能。分区主要关注数据的分区和存储,而分桶则重点考虑数据的分布以及查询效率。

二、环境准备

如果已经有了环境了,可以忽略,如果想快速部署环境可以参考我这篇文章:通过 docker-compose 快速部署 Hive 详细教程

三、外部表和管理表

在Hive中,可以创建两种类型的表:外部表和管理表。它们之间的主要区别如下:

1)外部表

1、外部表介绍

外部表是指在Hive中创建的表,实际上其数据是存储在外部文件系统(HDFS或本地文件系统)中的。

  • 外部分区表是一种特殊类型的表,它们的数据存储在Hive之外的文件系统上,例如HDFS、S3等。
  • 对于外部分区表,Hive只会管理它们的元数据信息,而不会管理数据文件本身,这意味着,如果你使用Hive命令删除一个外部分区表,只会删除该表的元数据,而不会删除数据文件
  • 外部分区表通常用于存储和管理原始数据,这些数据通常需要在多个系统和工具之间共享。

2、示例讲解

【示例一】下面是创建Hive外部表的一个示例(数据存储在HDFS):

假设我们有一个存储在 HDFS 上的数据文件,其路径为’/user/hive/external_table/data’,我们可以通过以下语句,在Hive中创建一个外部表:

# 登录容器docker exec -it hive-hiveserver2 
# 登录hive客户端beeline -u jdbc:hive2://hive-hiveserver2:10000  -n hadoop
# 建表
CREATE EXTERNAL TABLE external_table1 (
    column1 STRING,
    column2 INT,
    column3 DOUBLE)
ROW FORMAT DELIMITEDFIELDS TERMINATED BY ','LINES TERMINATED BY '\n'STORED AS TEXTFILELOCATION '/user/hive/external_table/data';

在该表中,我们指定了表的各列的数据类型和分隔符等信息,并且使用了LOCATION 关键字来指定数据文件的存储位置。这样,在Hive中对该外部表进行查询操作时,Hive会自动去对应的位置读取数据文件,并据此返回查询结果。

load 数据

# 模拟一些数据cat >data<<EOFc1,12,56.33c2,14,58.99c3,15,66.34c4,16,76.78EOF
# 登录hive客户端beeline -u jdbc:hive2://hive-hiveserver2:10000  -n hadoop# 加载数据,local 是加载本机文件数据load data local inpath './data' into table external_table1;

需要注意的是,在使用外部表时,我们必须保证Hive对数据文件的访问权限与HDFS的文件权限相同,否则会导致外部表的查询失败。此外,在使用外部表时,务必不要删除外部表的数据文件,否则将会导致查询结果的不准确。

【示例一】下面是创建外部表访问本地数据文件的示例(数据存储在本地,很少使用):

在Hive中,我们同样可以创建外部表来访问本地文件系统上的数据文件。在这种情况下,我们需要注意的是,在Hive的配置中,必须开启hive.stats.autogather 功能。否则,在查询外部表时可能会出现错误。

假设我们有一个存储在本地文件系统上的数据文件,路径为’/path/to/local/file’,我们可以通过以下语句,在Hive中创建一个外部表:

CREATE EXTERNAL TABLE external_table2 (
    column1 STRING,
    column2 INT,
    column3 DOUBLE)ROW FORMAT DELIMITEDFIELDS TERMINATED BY ','LINES TERMINATED BY '\n'STORED AS TEXTFILELOCATION 'file:///path/to/local/file';### hive文件存储格式包括以下几类(STORED AS TEXTFILE):#1、TEXTFILE#2、SEQUENCEFILE#3、RCFILE#4、ORCFILE(0.11以后出现)#其中TEXTFILE为默认格式,建表时不指定默认为这个格式,导入数据时会直接把数据文件拷贝到hdfs上不进行处理;

需要注意的是,我们在使用LOCATION关键字时,要指定为’file:///path/to/local/file’,而不是 ‘/path/to/local/file’ ,这是因为我们需要使用文件系统的URL来访问本地文件系统上的数据文件。

2)管理表(内部表)

1、管理表(内部表)介绍

管理表是利用Hive自身的存储能力来对数据进行存储和管理的表。在Hive中创建管理表时,必须指定数据的存储路径。

  • 管理表也称为内部表(Internal Table),管理表是Hive默认创建的表类型,它的数据存储在Hive默认的文件系统上(通常是HDFS)。
  • Hive会自动管理这些表的数据和元数据,包括表的位置、数据格式等。如果你使用Hive命令删除了一个管理表,那么该表的数据也会被删除
  • 通常情况下,管理表用于存储和管理中间结果、汇总数据和基础数据。当数据规模较小时,管理表是一个不错的选择,因为它可以提供更好的查询性能,同时也更容易管理。

2、示例讲解

在Hive中,除了外部表,我们还可以创建内部表来存储数据。与外部表不同的是,内部表存储的数据位于Hive自身管理的HDFS上,因此,在创建内部表时,我们需要确保数据可以被正确地上传到HDFS上。下面是创建内部表并存储在本机的示例:

假设我们有以下数据文件,名为data.csv,存储在本地文件系统的/path/to/local目录下:

cat >data.csv<<EOFvalue1,1,2.3value2,2,3.4value3,3,4.5EOF

我们可以使用以下语句,在Hive中创建一个内部表:

CREATE TABLE internal_table (    column1 STRING,    column2 INT,    column3 DOUBLE)ROW FORMAT DELIMITEDFIELDS TERMINATED BY ','LINES TERMINATED BY '\n'STORED AS TEXTFILE;# 加载本地数据,LOCAL LOAD DATA LOCAL INPATH './data.csv' INTO TABLE internal_table;# 加载HDFS数据# 先将文件推送到HDFS上hdfs dfs -put ./data.csv /tmp/# 登录hive客户端beeline -u jdbc:hive2://hive-hiveserver2:10000  -n hadoop# 加载HDFS上的数据LOAD DATA INPATH '/tmp/data.csv' INTO TABLE internal_table;# 查询select * from internal_table;


总之,外部表和管理表都可以在Hive中实现数据的存储和管理,但它们之间的不同主要体现在数据的存储和处理方式上。

四、分区表之静态分区和动态分区

Hive中的分区表可以进一步细分为静态分区和动态分区。

静态分区是指通过手动指定分区列的值来创建分区。例如,在创建一个基于年份的分区表时,我们可以手动指定每个分区名对应的年份:

CREATE TABLE sales (
  id int,
  date string,
  amount double)PARTITIONED BY (year string);
ALTER TABLE sales ADD PARTITION (year='2019') location '/data/sales/2022';
ALTER TABLE sales ADD PARTITION (year='2020') location '/data/sales/2023';

在上述示例中,我们通过 ALTER TABLE 语句手动添加了2019和2020两个年份的分区。

动态分区是指在加载数据时通过SQL语句自动创建分区。例如,在从一个包含销售记录的数据文件中加载数据时,可以自动根据数据中的年份信息创建相应的分区:

INSERT INTO TABLE sales PARTITION (year)SELECT id, date, amount, YEAR(date)FROM raw_sales;

在上述示例中,我们使用 PARTITION 子句指定在 CREATE TABLE 语句中定义的分区列year,并使用 YEAR(date) 表达式从数据中提取出年份信息。

动态分区的优点在于它可以大大简化创建和管理分区表的过程并提高效率;但是需要注意的是,它可能会在某些情况下产生不可预期的行为,例如可能创建太多分区。

总之,静态分区和动态分区都是用于在Hive中管理大型数据集的有效工具,具体使用需要根据具体情况选择最适合的方法,并理解它们的优点和缺点。

五、hive分区表严格模式和非严格模式

Hive分区表的严格模式和非严格模式可以通过以下两个参数进行设置:

  1. hive.exec.dynamic.partition.mode:该参数用于设置分区模式,其默认值为strict,即严格模式。可以将其设置为nonstrict,即非严格模式
# 登录hive客户端beeline -u jdbc:hive2://hive-hiveserver2:10000  -n hadoop# 设置SET hive.exec.dynamic.partition.mode=nonstrict;
  1. hive.exec.max.dynamic.partitions:该参数用于限制动态分区的最大数量。在非严格模式下,当动态分区的数量超过该参数指定的值时,Hive将抛出异常。可以通过以下语句来修改该参数:
SET hive.exec.max.dynamic.partitions=<value>;

其中,<value> 为一个整数值,表示限制的动态分区数量。如果需要取消该限制,可以将该参数设置为一个非正数,例如:

SET hive.exec.max.dynamic.partitions=-1;

需要注意的是,这些参数的设置仅对当前会话有效,也可以将其添加到Hive的配置文件中以在每个会话中自动应用。

总之,hive.exec.dynamic.partition.mode 和 hive.exec.max.dynamic.partitions 是控制Hive分区表严格模式和非严格模式的两个重要参数,开发人员可以根据自己的需求进行设置。

1)严格模式

严格模式要求在加载数据时必须指定所有分区列的值,否则将会导致抛出异常。例如,在下面的分区表中:

CREATE TABLE sales (
  id int,
  date string,
  amount double)PARTITIONED BY (year string, month string, day string)CLUSTERED BY (id) INTO 10 BUCKETS;

在严格模式下,我们必须为year、month和day三个分区列的所有可能取值指定一个分区:

INSERT INTO TABLE sales PARTITION (year='2019', month='01', day='03')SELECT id, date, amountFROM raw_salesWHERE YEAR(date) = 2019 AND MONTH(date) = 1 AND DAY(date) = 3;

在上述示例中,我们使用 PARTITION 子句手动为分区列year、month、day指定取值。

2)非严格模式

非严格模式则允许忽略某些分区列的值,这样使用 INSERT INTO 语句时只需指定提供的分区值即可。例如:

# SET hive.exec.dynamic.partition.mode=nonstrict;INSERT INTO TABLE sales PARTITION (year, month, day)SELECT id, YEAR(date), MONTH(date), DAY(date), amountFROM raw_salesWHERE YEAR(date) = 2019;

在上述示例中,我们使用 SET 语句设置分区模式为非严格模式,然后只提供了year分区列的值,而month和day分区列的值是从数据中动态计算得出的。

使用非严格模式可以简化分区表的创建和管理,但需要注意,它可能会产生一些意料之外的结果(例如可能创建太多分区),所以需要谨慎使用。

总之,分区表的严格模式和非严格模式都具有一些优点和缺点,具体使用需要根据具体情况选择最适合的方式。

六、分区表和分桶表示例讲解

1)分区表示例讲解

在Hive中,我们可以使用分区表来更有效地组织和管理数据。分区表将数据分为子集,每个子集对应一个或多个分区。这样,我们就可以更快地访问和查询数据,而不必扫描整个数据集。

创建分区表的语法类似于创建普通表,只不过要使用 PARTITIONED BY 子句指定一个或多个分区列,例如:

# 内部表CREATE TABLE partitioned_internal_table (
  id INT,
  mesg STRING)PARTITIONED BY (
  year INT,
  month INT)
ROW FORMAT DELIMITEDFIELDS TERMINATED BY ','LINES TERMINATED BY '\n'STORED AS TEXTFILE;
# 外部表
CREATE EXTERNAL TABLE partitioned_external_table (
  id INT,
  mesg STRING)PARTITIONED BY (
  year INT,
  month INT)
ROW FORMAT DELIMITEDFIELDS TERMINATED BY ','LINES TERMINATED BY '\n'STORED AS TEXTFILELOCATION '/user/hive/partitioned_table/data';

上述语句创建了一个分区表,在列column1和column2的基础上,按照year和month两列进行了分区。

【注意】分区的实现依赖于Hive的底层存储Hadoop分布式文件系统(HDFS)。为了确定如何分配数据,Hive要求每个分区对应一个目录,该目录包含该分区数据的所有文件。因此,在将数据加载到分区表中时,必须提供与分区对应的目录

例如,如果我们要将一个CSV文件加载到分区表中,我们可以使用以下语句:

LOAD DATA LOCAL INPATH './file.csv' INTO TABLE partitioned_external_table PARTITION (year=2019, month=1);# 查看分区show partitions partitioned_external_table;

在上述语句中,我们使用 LOAD DATA 子句将 /data/file.csv 文件加载到partitioned_table 表中,并指定了分区year为2019,分区month为1。

假设我们的CSV文件具有以下内容:

1,test1,2019,11,test2,2019,12,test3,2022,13,test4,2023,1

使用以下语句查询分区表:

SELECT * FROM partitioned_external_table WHERE year=2019 AND month=1;

分区表的优点在于可以更高效地组织数据,同时也允许我们根据需要删除或添加分区。例如,我们可以使用以下语句删除分区:

ALTER TABLE partitioned_table DROP PARTITION (year=2019, month=1);

可以使用以下语句添加分区:

ALTER TABLE partitioned_external_table ADD PARTITION (year=2020, month=2);
# 查看分区show partitions partitioned_external_table;

总之,分区表是管理和查询大型数据集的有效方式,可以帮助我们更轻松地处理大量数据。

2)分桶表示例讲解

除了分区表之外,Hive还提供了另一种将数据分割成可管理单元的方式,即分桶。

分区和分桶的概念有一些相似之处,但也存在一些重要的区别。

  • 分区是指基于表的某些列将数据分割成不同的存储单元;
  • 而分桶是指将数据根据哈希函数分成一组固定的桶。

类比于分区,在创建一个分桶表时,我们需要指定分桶的数量和分桶的列。例如,以下是一个创建分桶表的示例:

CREATE TABLE bucketed_table (
  column1 data_type,
  column2 data_type,  ...) 
CLUSTERED BY (column1) -- 分桶列INTO 10 BUCKETS; -- 桶数量

在上述示例中,我们将column1作为分桶列,并将数据分成10个桶。

加载数据时,Hive根据指定的桶列计算哈希值,并将数据存储在对应的桶中。

INSERT INTO TABLE bucketed_table VALUES ('value1', 1, 2.3)

查询时,可以使用以下格式指定桶列:

SELECT * FROM bucketed_table TABLESAMPLE(BUCKET x OUT OF y ON column1);

在上述示例中,我们使用用于抽样数据的 TABLESAMPLE 子句,指定从桶x中抽取数据,并在分桶列column1上进行抽样。

分桶表的优点在于,我们可以更容易地执行等值和范围查询,并更好地利用MapReduce 的数据本地性,从而提高查询性能。但分桶表也有一些缺点,例如添加和删除数据涉及重新计算哈希函数和移动数据的成本。

总之,分区表和分桶表都是Hive管理和处理大型数据集的重要工具,可以帮助我们更轻松地组织、查询和分析大量数据。在具体使用时,需要考虑表的存储和查询需求,选择最适合的表类型。在实际场景中分区用的居多。

关于Hive 分区和分桶的区别和示例讲解就先到这里了,有任何疑问欢迎给我留言,后续会持续更新相关文章,请小伙伴耐心等待

from:https://baijiahao.baidu.com/s?id=1764915870006249443&wfr=spider&for=pc

hive 响应慢问题定位

情景描述:

大数据集群,目前有两套hiveserver2和metastore的集群,通过nginx指向进行流量互切,发现流量打到哪个metastore集群,哪个集群就特别卡顿。

那么先来回顾一下hive整个调用流程和框架

image-20201227123352163

Hive 提供的另外一个shell 客户端,也就是我们常用的hive 命令的客户端它的设计是直接启动了一个org.apache.hadoop.hive.cli.CliDriver的进程,这个进程其实主要包含了两块内容一个是提供给我们交互的cli ,另外一个就是我们的Driver 驱动引擎,这样的设计导致如果我们有多个客户端的情况下,我们就需要有多个Driver

image-20201226204741803

但是我们通过HiveServer2连接的时候我们就可以共享Driver,一方面可以简化客户端的设计降低资源损耗,另外一方面还能降低对MetaStore 的压力,减少连接的个数。

原因分析:

目前来看,变慢的原因应该是出现在hs2服务,metastore服务,具体业务,网络,服务器等原因。

1、先从简单的硬件分析入手,验证网络和服务器,这块省略验证过程

2、验证hs2服务,利用排除的方式,通过hive cli进行多次验证,发现也有缓慢的情况,正常应该1秒内返回,所以先不定位hs2的情况

3、验证metastore服务,还是先从直观简单的分析,看能否找出些现象,先看日志:

3.1 既然通过cli和hs2访问都会慢,先从简单的cli发起请求,可以通过加debug参数,发起查询

beeline –verbose=true –showNestedErrs=true –debug=true 看一下客户端是否有明显异常

3.2 cd /var/log/hive 查看一下是否有大量客户端访问

cat hadoop-cmf-hive-HIVEMETASTORE-data-hadoop-16-2.192.168.0.1.log.out | grep audit | grep -v “ugi=hue” | awk -F “ip=” ‘{print $2}’ | awk ‘{print $1}’ | sort | uniq -c | sort -nr | head

3.3 查看服务GC情况 jstat -gcutil pid interval(ms)

3.4 查看服务端日志

Error: Error while compiling statement: [Error 10308]: Attempt to acquire compile lock timed out. (state=,code=10308)
org.apache.hive.service.cli.HiveSQLException: Error while compiling statement: [Error 10308]: Attempt to acquire compile lock timed out.
at org.apache.hive.jdbc.Utils.verifySuccess(Utils.java:241)
at org.apache.hive.jdbc.Utils.verifySuccessWithInfo(Utils.java:227)
at org.apache.hive.jdbc.HiveStatement.execute(HiveStatement.java:255)
at org.apache.hive.beeline.Commands.executeInternal(Commands.java:989)
at org.apache.hive.beeline.Commands.execute(Commands.java:1180)
at org.apache.hive.beeline.Commands.sql(Commands.java:1094)
at org.apache.hive.beeline.BeeLine.dispatch(BeeLine.java:1180)
at org.apache.hive.beeline.BeeLine.execute(BeeLine.java:1013)
at org.apache.hive.beeline.BeeLine.begin(BeeLine.java:922)
at org.apache.hive.beeline.BeeLine.mainWithInputRedirection(BeeLine.java:518)
at org.apache.hive.beeline.BeeLine.main(BeeLine.java:501)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.apache.hadoop.util.RunJar.run(RunJar.java:226)
at org.apache.hadoop.util.RunJar.main(RunJar.java:141)
Caused by: org.apache.hive.service.cli.HiveSQLException: Error while compiling statement: [Error 10308]: Attempt to acquire compile lock timed out.
at org.apache.hive.service.cli.operation.Operation.toSQLException(Operation.java:400)
at org.apache.hive.service.cli.operation.SQLOperation.prepare(SQLOperation.java:187)
at org.apache.hive.service.cli.operation.SQLOperation.runInternal(SQLOperation.java:271)
at org.apache.hive.service.cli.operation.Operation.run(Operation.java:337)
at org.apache.hive.service.cli.session.HiveSessionImpl.executeStatementInternal(HiveSessionImpl.java:439)
at org.apache.hive.service.cli.session.HiveSessionImpl.executeStatementAsync(HiveSessionImpl.java:416)
at org.apache.hive.service.cli.CLIService.executeStatementAsync(CLIService.java:282)
at org.apache.hive.service.cli.thrift.ThriftCLIService.ExecuteStatement(ThriftCLIService.java:503)
at org.apache.hive.service.cli.thrift.TCLIService$Processor$ExecuteStatement.getResult(TCLIService.java:1313)
at org.apache.hive.service.cli.thrift.TCLIService$Processor$ExecuteStatement.getResult(TCLIService.java:1298)
at org.apache.thrift.ProcessFunction.process(ProcessFunction.java:39)
at org.apache.thrift.TBaseProcessor.process(TBaseProcessor.java:39)
at org.apache.hive.service.auth.TSetIpAddressProcessor.process(TSetIpAddressProcessor.java:56)
at org.apache.thrift.server.TThreadPoolServer$WorkerProcess.run(TThreadPoolServer.java:286)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)

这里显示有获取metastore连接超时的异常,关键的日志是compiling statement: [Error 10308]: Attempt to acquire compile lock timed out. 编译时候获取编译索失败

顺着这个思路,查看一下代码:

关键字:Completed compiling command(queryId,参考https://github.com/apache/hive/blob/master/ql/src/java/org/apache/hadoop/hive/ql/lock/CompileLock.java

分析结论如下:

并且hivethrift每次访问都会初始化metastore 重新初始化元数据,HIVESEVER2提交SQL阻塞tryAcquireCompileLock原因:

a、HIVE1.1 只支持串行编译SQL,hiveserver2并发接受到SQL请求后,在complile阶段变为串行执行。当compilie编译慢时,引起阻塞SQL的提交。

b、compile的慢的原因:complile阶段,会通过hivemetastore访问mysql。目前是hiveserver2的请求打入到同一个metastore,流量上来后,hivemetastore访问mysql速度下降。

解决方案:hiveserver2的请求分摊所有hivemetastore上