数据, 术→技巧, 法→原理

大数据通识:HIVE

钱魏Way · · 79 次浏览

Hive简介

Hive 由 Facebook 实现并开源,是基于 Hadoop 的一个数据仓库工具。可以将结构化的数据映射为一张数据库表并提供 HQL(Hive SQL)查询功能。底层数据是存储在 HDFS 上,Hive的本质是将 SQL 语句转换为 MapReduce 任务运行,使不熟悉 MapReduce 的用户很方便地利用 HQL 处理和计算 HDFS 上的结构化的数据,适用于离线的批量数据计算。

Hive与普通关系型数据库的区别:

  • 查询语言。Hive提供了类 SQL 的查询语言 HQL,熟悉SQL的开发者可直接使用。
  • 数据存储位置。Hive 是建立在 Hadoop 之上的,所有 Hive 的数据都是存储在 HDFS 中的。而数据库则可以将数据保存在块设备或者本地文件系统中。
  • 数据格式。Hive 中没有定义专门的数据格式,数据格式可以由用户指定,用户定义数据格式需要指定三个属性:列分隔符(通常为空格、\t、\x001)、行分隔符(\n)以及读取文件数据的方法(Hive 中默认有三个文件格式 TextFile,SequenceFile 以及 RCFile)。由于在加载数据的过程中,不需要从用户数据格式到 Hive 定义的数据格式的转换,因此,Hive 在加载的过程中不会对数据本身进行任何修改,而只是将数据内容复制或者移动到相应的 HDFS 目录中。而在数据库中,不同的数据库有不同的存储引擎,定义了自己的数据格式。所有数据都会按照一定的组织存储,因此,数据库加载数据的过程会比较耗时。
  • 数据更新。由于 Hive 是针对数据仓库应用设计的,而数据仓库的内容是读多写少的。因此,Hive 中不支持对数据的改写和添加,所有的数据都是在加载的时候中确定好的。而数据库中的数据通常是需要经常进行修改的,因此可以使用INSERT INTO … VALUES 添加数据,使用UPDATE … SET修改数据。
  • 索引。Hive 在加载数据的过程中不会对数据进行任何处理,甚至不会对数据进行扫描,因此也没有对数据中的某些 Key 建立索引。Hive 要访问数据中满足条件的特定值时,需要暴力扫描整个数据,因此访问延迟较高。由于 MapReduce 的引入, Hive 可以并行访问数据,因此即使没有索引,对于大数据量的访问,Hive 仍然可以体现出优势。数据库中,通常会针对一个或者几个列建立索引,因此对于少量的特定条件的数据的访问,数据库可以有很高的效率,较低的延迟。由于数据的访问延迟较高,决定了 Hive 不适合在线数据查询。
  • 执行。Hive 中大多数查询的执行是通过 Hadoop 提供的 MapReduce 来实现的。而数据库通常有自己的执行引擎。
  • 执行延迟。Hive 在查询数据的时候,由于没有索引,需要扫描整个表,因此延迟较高。另外一个导致 Hive 执行延迟高的因素是 MapReduce 框架。由于 MapReduce 本身具有较高的延迟,因此在利用 MapReduce 执行 Hive 查询时,也会有较高的延迟。相对的,数据库的执行延迟较低。当然,这个低是有条件的,即数据规模较小,当数据规模大到超过数据库的处理能力的时候,Hive 的并行计算显然能体现出优势。
  • 可扩展性。由于 Hive 是建立在 Hadoop 之上的,因此 Hive 的可扩展性是和 Hadoop 的可扩展性是一致的。而数据库由于 ACID 语义的严格限制,扩展行非常有限。目前最先进的并行数据库 Oracle 在理论上的扩展能力也只有 100 台左右。
  • 数据规模。由于 Hive 建立在集群上并可以利用 MapReduce 进行并行计算,因此可以支持很大规模的数据;对应的数据库可以支持的数据规模较小。

需明确的是,Hive 作为数仓应用工具,对比 RDBMS(关系型数据库) 有3个“不能”:

  • 不能像 RDBMS 一般实时响应,Hive 查询延时大
  • 不能像 RDBMS 做事务型查询,Hive 没有事务机制
  • 不能像 RDBMS 做行级别的变更操作(包括插入、更新、删除)

另外,Hive 相比 RDBMS 是一个更“宽松”的世界,比如:

  • Hive 没有定长的 varchar 这种类型,字符串都是 string
  • Hive 是读时模式,它在保存表数据时不会对数据进行校验,而是在读数据时校验不符合格式的数据设置为NULL

Hive的架构

由上图可知,hadoop和mapreduce是hive架构的根基。Hive架构包括如下组件:CLI(command line interface)、JDBC/ODBC、Thrift Server、WEB GUI、metastore和Driver(Complier、Optimizer和Executor),这些组件可以分为两大类:

  • 服务端组件:
    • Driver组件:该组件包括Complier、Optimizer和Executor,它的作用是将我们写的HiveQL(类SQL)语句进行解析、编译优化,生成执行计划,然后调用底层的mapreduce计算框架。
    • Metastore组件:元数据服务组件,这个组件存储hive的元数据,hive的元数据存储在关系数据库里,hive支持的关系数据库有derby、mysql。元数据对于hive十分重要,因此hive支持把metastore服务独立出来,安装到远程的服务器集群里,从而解耦hive服务和metastore服务,保证hive运行的健壮性,这个方面的知识,我会在后面的metastore小节里做详细的讲解。
    • Thrift服务:thrift是facebook开发的一个软件框架,它用来进行可扩展且跨语言的服务的开发,hive集成了该服务,能让不同的编程语言调用hive的接口。
  • 客户端组件:
    • CLI:command line interface,命令行接口。
    • Thrift客户端:上面的架构图里没有写上Thrift客户端,但是hive架构的许多客户端接口是建立在thrift客户端之上,包括JDBC和ODBC接口。
    • WEB GUI:hive客户端提供了一种通过网页的方式访问hive所提供的服务。这个接口对应hive的hwi组件(hive web interface),使用前要启动hwi服务。

Hive数据存储

Hive 的存储结构包括数据库、表、视图、分区和表数据等。数据库,表,分区等等都对 应 HDFS 上的一个目录。表数据对应 HDFS 对应目录下的文件。Hive 中所有的数据都存储在 HDFS 中,没有专门的数据存储格式,因为 Hive 是读模式 (Schema On Read),可支持 TextFile,SequenceFile,RCFile 或者自定义格式等。

Hive 没有专门的数据存储格式,也没有为数据建立索引,用户可以非常自由的组织 Hive 中的表,只需要在创建表的时候告诉 Hive 数据中的列分隔符和行分隔符,Hive 就可以解析数据。其次,Hive 中所有的数据都存储在 HDFS 中,Hive 中包含以下数据模型:Table,External Table,Partition,Bucket。

  • Hive 中的 Table 和数据库中的 Table 在概念上是类似的,每一个 Table 在 Hive 中都有一个相应的目录存储数据。例如,一个表 pvs,它在 HDFS 中的路径为:/wh/pvs,其中,wh 是在 hive-site.xml 中由 ${hive.metastore.warehouse.dir} 指定的数据仓库的目录,所有的 Table 数据(不包括 External Table)都保存在这个目录中。
  • Partition 对应于数据库中的 Partition 列的密集索引,但是 Hive 中 Partition 的组织方式和数据库中的很不相同。在 Hive 中,表中的一个 Partition 对应于表下的一个目录,所有的 Partition 的数据都存储在对应的目录中。例如:pvs 表中包含 ds 和 city 两个 Partition,则对应于 ds = 20090801, ctry = US 的 HDFS 子目录为:/wh/pvs/ds=20090801/ctry=US;对应于 ds = 20090801, ctry = CA 的 HDFS 子目录为;/wh/pvs/ds=20090801/ctry=CA
  • Buckets 对指定列计算 hash,根据 hash 值切分数据,目的是为了并行,每一个 Bucket 对应一个文件。将 user 列分散至 32 个 bucket,首先对 user 列的值计算 hash,对应 hash 值为 0 的 HDFS 目录为:/wh/pvs/ds=20090801/ctry=US/part-00000;hash 值为 20 的 HDFS 目录为:/wh/pvs/ds=20090801/ctry=US/part-00020
  • External Table 指向已经在 HDFS中存在的数据,可以创建 Partition。它和 Table在元数据的组织上是相同的,而实际数据的存储则有较大的差异。
    • Table 的创建过程和数据加载过程(这两个过程可以在同一个语句中完成),在加载数据的过程中,实际数据会被移动到数据仓库目录中,之后对数据对访问将会直接在数据仓库目录中完成。删除表时,表中的数据和元数据将会被同时删除。
    • External Table 只有一个过程,加载数据和创建表同时完成(CREATE EXTERNAL TABLE ……LOCATION),实际数据是存储在 LOCATION 后面指定的 HDFS 路径中,并不会移动到数据仓库目录中。当删除一个 External Table 时,仅删除。

Hive元数据存储

Hive的metastore组件是hive元数据集中存放地。Metastore组件包括两个部分:metastore服务和后台数据的存储。后台数据存储的介质就是关系数据库,例如hive默认的嵌入式磁盘数据库derby,还有mysql数据库。Metastore服务是建立在后台数据存储介质之上,并且可以和hive服务进行交互的服务组件,默认情况下,metastore服务和hive服务是安装在一起的,运行在同一个进程当中。我也可以把metastore服务从hive服务里剥离出来,metastore独立安装在一个集群里,hive远程调用metastore服务,这样我们可以把元数据这一层放到防火墙之后,客户端访问hive服务,就可以连接到元数据这一层,从而提供了更好的管理性和安全保障。使用远程的metastore服务,可以让metastore服务和hive服务运行在不同的进程里,这样也保证了hive的稳定性,提升了hive服务的效率。

Hive 将元数据存储在 RDBMS 中,有三种模式可以连接到数据库:

  • Single User Mode: 此模式连接到一个 In-memory 的数据库 Derby,一般用于 Unit Test。
  • Multi User Mode:通过网络连接到一个数据库中,是最经常使用到的模式。
  • Remote Server Mode:用于非 Java 客户端访问元数据库,在服务器端启动一个 MetaStoreServer,客户端利用 Thrift 协议通过 MetaStoreServer 访问元数据库。

Hive字段类型

分类 类型 描述 字面量示例
原始类型 BOOLEAN true/false TRUE
TINYINT 1字节的有符号整数 -128~127 1Y
SMALLINT 2个字节的有符号整数,-32768~32767 1S
INT 4个字节的带符号整数 1
BIGINT 8个字节的带符号整数 1L
FLOAT 4字节单精度浮点数 1.0
DOUBLE 8字节单精度浮点数 1.0
DEICIMAL 任意精度的带符号小数 1.0
STRING 字符串,变长 abc
VARCHAR 变长字符串 abc
CHAR 固定长度字符串 abc
BINARY 字节数组 无法表示
TIMESTAMP 时间戳,毫秒精度 1642123232761
DATE 日期 2022-01-14
INTERVAL 时间频率间隔
复杂类型 ARRAY 有序的的同类型的集合 array(1,2)
MAP key-value,key必须为原始类型,value可以任意类型 map(‘a’,1,’b’,2)
STRUCT 字段集合,类型可以不同 struct(‘1’,1,1.0), named_stract(‘col1’,’1’,’col2’,1,’clo3’,1.0)
UNION 在有限取值范围内的一个值 create_union(1,’a’,63)

decimal用法:用法:decimal(11,2) 代表最多有11位数字,其中后2位是小数,整数部分是9位;如果整数部分超过9位,则这个字段就会变成null;如果小数部分不足2位,则后面用0补齐两位,如果小数部分超过两位,则超出部分四舍五入。也可直接写 decimal,后面不指定位数,默认是 decimal(10,0)  整数10位,没有小数

Hive SQL简介

Hive 查询语句

Hive Select 常规语法与 Mysql 等 RDBMS SQL 几乎无异,下面附注语法格式,具体不做详细讲解。

SELECT 语法及语序

SELECT [ALL | DISTINCT] select_expr, select_expr, ...
FROM table_reference
[WHERE where_condition]
[GROUP BY col_list]
[ORDER BY order_condition]
[DISTRIBUTE BY distribute_condition [SORT BY sort_condition] ]
[LIMIT number]

多维度聚合分析 grouping sets/cube/roolup

不使用多维聚合方法:

SELECT NULL, NULL, NULL, COUNT(*)
FROM requests
UNION ALL
SELECT os, device, NULL, COUNT(*)
FROM requests GROUP BY os, device
UNION ALL
SELECT null, null, city, COUNT(*)
FROM requests GROUP BY city;

使用 grouping sets:

SELECT os, device, city ,COUNT(*)
FROM requests
GROUP BY os, device, city GROUPING SETS((os, device), (city), ());

cube 会枚举指定列的所有可能组合作为 grouping sets,而 roolup 会以按层级聚合的方式产生 grouping sets。如:

GROUP BY CUBE(a, b, c)  
--等价于以下语句。  
GROUPING SETS((a,b,c),(a,b),(a,c),(b,c),(a),(b),(c),())

GROUP BY ROLLUP(a, b, c)
--等价于以下语句。  
GROUPING SETS((a,b,c),(a,b),(a), ())

正则方法指定 SELECT 字段列

说是指定,其实是排除,如:`(num|uid)?+.+` 排除 num 和 uid 字段列。另外,where 使用正则可以如此:where A Rlike B、where A Regexp B。

Lateral View(一行变多行)

Lateral View 和表生成函数(例如Split、Explode等函数)结合使用,它能够将一行数据拆成多行数据,并对拆分后的数据进行聚合。

假设您有一张表pageAds,它有两列数据,第一列是pageid string,第二列是adid_list,即用逗号分隔的广告ID集合。现需要统计所有广告在所有页面的出现次数,则先用 Lateral View + explode 做处理,即可正常分组聚合统计:

SELECT pageid, adid FROM pageAds LATERAL VIEW explode(adid_list) adTable AS adid;

窗口函数

Hive 的窗口函数非常丰富, 其中最常用的窗口函数当属 row_number() over(partition by col order col_2),它可以实现按指定字段的分组排序。

  • COUNT 计算计数值。
  • AVG 计算平均值。
  • MAX 计算最大值。
  • MIN 计算最小值。
  • MEDIAN 计算中位数。
  • STDDEV 计算总体标准差。
  • STDDEV_SAMP 计算样本标准差。
  • SUM 计算汇总值。
  • DENSE_RANK 计算连续排名。
  • RANK 计算跳跃排名。
  • LAG 按偏移量取当前行之前第几行的值。
  • LEAD 按偏移量取当前行之后第几行的值。
  • PERCENT_RANK 计算一组数据中某行的相对排名。
  • ROW_NUMBER 计算行号。
  • CLUSTER_SAMPLE 用于分组抽样。
  • CUME_DIST 计算累计分布。
  • NTILE 将分组数据按照顺序切片,并返回切片值。

Hive定义变量

SET aa='10';
SELECT ${hiveconf:aa};

SET hivevar:aa='10';
SELECT ${hivevar:aa};

SET hiveconf:aa='10';
SELECT ${hiveconf:aa};

CTE语法和定义变量

with t1 as(
    select user_id
    from user
    where ...
)

@var:= select
         shop_id
       from shop
       where ...;

select *
from user_shop
where user_id in(select * from t1)
and shop_id in(select * from @var);

Hive 定义语句

Hive 建表语句格式

方法一:独立声明

CREATE [EXTERNAL] TABLE [IF NOT EXISTS] table_name
[(col_name data_type [DEFAULT value] [COMMENT col_comment], ...)]
[COMMENT table_comment]
[PARTITIONED BY (col_name data_type [COMMENT col_comment], ...)]
[CLUSTERED BY (col_name [, col_name, ...]) [SORTED BY (col_name [ASC | DESC] [, col_name [ASC | DESC] ...])] INTO number_of_buckets BUCKETS] 
[STORED BY StorageHandler] -- 仅限外部表
[WITH SERDEPROPERTIES (Options)] -- 仅限外部表
[LOCATION OSSLocation]; -- 仅限外部表
[LIFECYCLE days]
[AS select_statement]

方法二:从已有表直接复制

CREATE TABLE [IF NOT EXISTS] table_name LIKE existing_table_name

下面对当中关键的声明语句做解释:

  • [EXTERNAL]:声明为外部表,往往在该表需要被多个工具共享时声明,外部表删表不会删数据,只会删元数据。
  • col_name datatype:data_type 一定要严谨定义,避免 bigint、double 等等统统用 string 的偷懒做法,否则不知某天数据就出错了。(团队内曾有同事犯过此错误)
  • [if not exists]:创建时不指定,若存在同名表则返回出错。指定此选项,若存在同名表忽略后续,不存在则创建。
  • [DEFAULT value]:指定列的默认值,当INSERT操作不指定该列时,该列写入默认值。
  • [PARTITIONED BY]:指定表的分区字段,当利用分区字段对表进行分区时,新增分区、更新分区内数据和读取分区数据均不需做全表扫描,可以提高处理效率。
  • [LIFECYCLE]:是表的生命周期,分区表则每个分区的生命周期与表生命周期相同
  • [AS select_statement]:意味着可直接跟 select 语句插入数据

简单示例:创建表sale_detail来保存销售记录,该表使用销售时间 sale_date 和销售区域 region 作为分区列。

create table if not exists sale_detail
(
shop_name     string,
customer_id   string,
total_price   double
)
partitioned by (sale_date string, region string);

创建成功的表可以通过 desc 查看定义信息:

desc <table_name>;
desc extended <table_name>; --查看外部表信息。

如果需要不记得完整的表名,可以通过 show tables 在 db(数据库)范围内查找:

use db_name;
show tables ('tb.*'); --- tb.* 为正则表达式

Hive 删表语句格式

DROP TABLE [IF EXISTS] table_name;  --- 删除表
ALTER TABLE table_name DROP [IF EXISTS] PARTITION (partition_col1 = partition_col_value1,  ...); --- 删除某分区

Hive 变更表定义语句格式

ALTER TABLE table_name RENAME TO table_name_new;  --- 重命名表
ALTER TABLE table_name ADD [IF NOT EXISTS] PARTITION (partition_col1 = partition_col_value1 ...);  --- 增加分区
ALTER TABLE table_name ADD COLUMNS (col_name1 type1 comment 'XXX');  --- 增加列,同时定义类型与注释
ALTER TABLE table_name CHANGE COLUMN old_col_name new_col_name column_type COMMENT column_comment;  --- 修改列名和注释
ALTER TABLE table_name SET lifecycle days;  --- 修改生命周期

Hive 操作语句

Hive insert语句格式:

INSERT OVERWRITE|INTO TABLE tablename [PARTITION (partcol1=val1...]
select_statement
FROM from_statement;

下面对当中关键的声明语句做解释:

  • into|overwrite:into-直接向表或表的分区中追加数据;先清空表中的原有数据,再向表或分区中插入数据。
  • [PARTITION (partcol1=val1…]:不允许使用函数等表达式,只能是常量。

关于 PARTITION 这里展开说明指定分区插入和动态分区插入

  • 输出到指定分区:在INSERT语句中直接指定分区值,将数据插入指定的分区。
  • 输出到动态分区:在INSERT语句中不直接指定分区值,只指定分区列名。分区列的值在SELECT子句中提供,系统自动根据分区字段的值将数据插入到相应分区。

HIVE SQL优化

列裁剪

例如某表有a,b,c,d,e五个字段,但是我们只需要a和b,那么请用select a,b from table 而不是select * from table

分区裁剪

在查询的过程中减少不必要的分区,即尽量指定分区

小表放前大表放后

在编写带有join的代码语句时,应该将条目少的表/子查询放在join操作符的前面

因为在Reduce阶段,位于join操作符左边的表会先被加载到内存,载入条目较少的表可以有效的防止内存溢出(OOM)。所以对于同一个key来说,对应的value值小的放前面,大的放后面

尽量避免使用distinct

尽量避免使用distinct进行重排,特别是大表,容易产生数据倾斜(key一样在一个reduce处理)。使用group by替代:

select distinct key from a
select key from a group by key

参考链接:

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注