目录
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
参考链接: