- 1 分区表
- 1.1 Hive查询基本原理
- 1.2 普通表结构问题
- 1.3 分区表设计思想
- 1.4 分区表测试
- 2 分桶表
- 2.1 Hive中Join的问题
- 2.2 分桶表设计思想
- 2.3 分桶表测试
- 3 索引设计
- 3.1 Hive中的索引
- 3.2 索引的原理及使用
- 3.3 索引的问题与应用
1 分区表 1.1 Hive查询基本原理
Hive的设计思想是通过元数据将HDFS上的文件映射成表,基本的查询原理是当用户通过HQL
语句对Hive中的表进行复杂数据处理和计算时,默认将其转换为分布式计算MapReduce程序对
HDFS中的数据进行读取处理的过程。
例如,当我们在Hive中创建一张表tb_login并关联HDFS上的文件,用于存储所有用户的登录信
息,当我们对这张表查询数据时,Hive中的实现过程如下:
⚫ step1:创建表
--创建数据库 create database tb_part; --创建表 create table tb_login( userid string, logindate string ) row format delimited fields terminated by ' 't' ';
◼ HDFS中自动在Hive数据仓库的目录下和对应的数据库目录下,创建表的目录
⚫ step2:关联数据
load data local inpath '/export/data/login.log' into table tb_login;
◼ 数据会被自动放入HDFS中对应的表的目录下
◼ 数据在表中可以被正常读取
⚫ step3:查询数据
-- 统计3月24号的登录人数 select logindate, count(*) as cnt from tb_login where logindate = '2021- - 03- - 24' group by logindate;
◼ 当执行查询计划时,Hive会使用表的最后一级目录作为底层处理数据的输入
◼ 先根据表名在元数据中进行查询表对应的HDFS目录
◼ 然后将整个HDFS中表的目录作为底层查询的输入,可以通过explain命令查看执行计
划依赖的数据
explain extended select logindate, count ( * ) as cnt from tb_login where logindate = '2021- - 03- - 24' group by logindate;1.2 普通表结构问题
默认的普通表结构中,表的最后一级目录就是表的目录,而底层的计算会使用表的最后一级目
录作为Input进行计算,这种场景下,我们就会遇到一个问题,如果表的数据很多,而我们需要被
处理的数据很少,只是其中一小部分,这样就会导致大量不必要的数据被程序加载,在程序中被过
滤,导致大量不必要的计算资源的浪费。
例如,上面的需求中,只需要对2021-03-24日的数据进行计算,但实际上由于表结构的设计,
在底层执行MapReduce时,将整张表的数据都进行了加载,MapReduce程序中必须对所有数据进
行过滤,将3月24号的数据过滤出来,再进行处理。假设每天有1G的数据增量,一年就是365GB的
数据,按照业务需求,我们每次只需要对其中一天的数据进行处理,也就是处理1GB的数据,程序
会先加载365GB的数据,然后将364GB的数据过滤掉,只保留一天的数据再进行计算,导致了大量
的磁盘和网络的IO的损耗。
针对上面的问题,Hive提供了一种特殊的表结构来解决——分区表结构。分区表结构的设计思
想是:根据查询的需求,将数据按照查询的条件【一般都以时间】进行划分分区存储,将不同分区
的数据单独使用一个HDFS目录来进行存储,当底层实现计算时,根据查询的条件,只读取对应分
区的数据作为输入,减少不必要的数据加载,提高程序的性能。
例如,上面的需求中,我们可以将每天的用户登录数据,按照登陆日期进行分区存储到Hive
表中,每一天一个分区,在HDFS的底层就可以自动实现将每天的数据存储在不同的目录中,当用
户查询某天的数据时,可以直接使用这一天的分区目录进行处理,不需要加载其他数据。
基于分区表的设计实现将所有用户的登录信息进行分区存储
⚫ 创建分区表:按照登陆日期分区
–创建表
create table tb_login_part(
userid string
)
partitioned by (logindate string)
row format delimited fields terminated by ’ ‘t’ ';
⚫ 将所有登陆数据写入分区表,分区存储
–开启动态分区
set hive.exec.dynamic.partition.mode=nonstrict;
–按登录日期分区
insert into table tb_login_part partition(logindate)
select * from tb_login;
◼ HDFS中会自动在表的目录下,为每个分区创建一个分区目录
⚫ 查询2021-03-23或者2021-03-24的数据进行统计
select
logindate,
count ( * ) as cnt
from tb_login_part
where logindate = ‘2021- - 03- - 23’ or logindate = ‘2021- - 03- - 24’
group by logindate;
◼ 查询先检索元数据,元数据中记录该表为分区表并且查询过滤条件为分区字段,所以
找到该分区对应的HDFS目录
◼ 加载对应分区的目录作为计算程序的输入
⚫ 查看执行计划
◼ 如果不做分区表
explain extended
select
logindate,
count ( * ) as cnt
from tb_login
where logindate = ‘2021- - 03- - 23’ or logindate = ‘2021- - 03- - 24’
group by logindate;
◼ 如果做了分区表
explain extended
select
logindate,
count ( * ) as cnt
from tb_login_part
where logindate = ‘2021- - 03- - 23’ or logindate = ‘2021- - 03- - 24’
group by logindate;
表的Join是数据分析处理过程中必不可少的 *** 作,Hive同样支持Join的语法,Hive Join的底层
还是通过MapReduce来实现的,但是Hive实现Join时面临一个问题:如果有两张非常大的表要进行
Join,两张表的数据量都很大,Hive底层通过MapReduce实现时,无法使用MapJoin提高Join的性
能,只能走默认的ReduceJoin,而ReduceJoin必须经过Shuffle过程,相对性能比较差,而且容易
产生数据倾斜,如何解决这个问题?
针对以上的问题,Hive中提供了另外一种表的结构——分桶表结构。分桶表的设计有别于分区
表的设计,分区表是将数据划分不同的目录进行存储,而分桶表是将数据划分不同的文件进行存储。
分桶表的设计是按照一定的规则【通过MapReduce中的多个Reduce来实现】将数据划分到不同的
文件中进行存储,构建分桶表。
如果有两张表按照相同的划分规则【按照Join的关联字段】将各自的数据进行划分,在Join时,
就可以实现Bucket与Bucket的Join,避免不必要的比较。
例如:当前有两张表,订单表有1000万条,用户表有10万条,两张表的关联字段是userid,现
在要实现两张表的Join。
我们将订单表按照userid划分为3个桶,1000万条数据按照userid的hash取余存储在对应的
Bucket中。
同理,我们再将用户表按照相同的规则,存储在3个桶中。
在Join时,只需要将两张表的Bucket0与Bucket0进行Join,Bucket1与Bucket1进行Join,Bucket2
与Bucket2进行Join即可,不用让所有的数据挨个比较,降低了比较次数,提高了Join的性能。
当前有两份较大的数据文件,emp员工数据和dept部门数据,现在要基于Hive实现两张表的
Join,我们可以通过分桶实现分桶Join提高性能。
⚫ 构建普通emp表
create database if not exists db_emp; use db_emp; --创建普通表 create table tb_emp01( empno string, ename string, job string, managerid string, hiredate string, salary double, jiangjin double, deptno string ) row format delimited fields terminated by ' 't' '; --加载数据 load data local inpath '/export/data/emp01.txt' into table tb_emp01;
⚫ 构建分桶emp表
use db_emp; -- 创建分桶表 create table tb_emp02( empno string, ename string, job string, managerid string, hiredate string, salary double, jiangjin double, deptno string ) clustered by(deptno) sorted by (deptno asc) into 3 buckets row format delimited fields terminated by ' 't' '; -- 写入分桶表 insert overwrite table tb_emp02 select * from tb_emp01;
⚫ 构建普通dept表
use db_emp; -- 创建部门表 create table tb_dept01( deptno string, dname string, loc string ) row format delimited fields terminated by ','; -- 加载数据 load data local inpath '/export/data/dept01.txt' into table tb_dept01;
⚫ 构建分桶dept表
use db_emp; --创建分桶表 create table tb_dept02( deptno string, dname string, loc string ) clustered by(deptno) sorted by (deptno asc) into 3 buckets row format delimited fields terminated by ','; --写入分桶表 insert overwrite table tb_dept02 select * from tb_dept01;
⚫ 普通的Join执行计划
explain select a.empno, a.ename, a.salary, b.deptno, b.dname from tb_emp01 a join tb_dept01 b on a.deptno = b.deptno;
⚫ 分桶的Join执行计划
--开启分桶 SMB join set hive.optimize.bucketmapjoin = true; set hive.auto.convert.sortmerge.join= true; set hive.optimize.bucketmapjoin.sortedmerge = true; --查看执行计划 explain select a.empno, a.ename, a.salary, b.deptno, b.dname from tb_emp02 a join tb_dept02 b on a.deptno = b.deptno;3 索引设计 3.1 Hive中的索引
在传统的关系型数据库例如MySQL、Oracle等数据库中,为了提高数据的查询效率,可以为表
中的字段单独构建索引,查询时,可以基于字段的索引快速的实现查询、过滤等 *** 作。
Hive中也同样提供了索引的设计,允许用户为字段构建索引,提高数据的查询效率。但是Hive
的索引与关系型数据库中的索引并不相同,比如,Hive不支持主键或者外键。Hive索引可以建立在
表中的某些列上,以提升一些 *** 作的效率,例如减少MapReduce任务中需要读取的数据块的数量。
在可以预见到分区数据非常庞大的情况下,分桶和索引常常是优于分区的。而分桶由于SMB
Join对关联键要求严格,所以并不是总能生效。官方文档:
https://cwiki.apache.org/confluence/display/Hive/LanguageManual+DDL#LanguageManualDD L-Create/Drop/AlterIndex
注意:官方明确表示,索引功能支持是从Hive0.7版本开始,到Hive3.0不再支持。
Hive中索引的基本原理:当为某张表的某个字段创建索引时,Hive中会自动创建一张索引表,
该表记录了该字段的每个值与数据实际物理位置之间的关系,例如数据所在的HDFS文件地址,以
及所在文件中偏移量offset等信息。
Hive的索引目的是提高Hive表指定列的查询速度。没有索引时,类似’WHERe tab1.col1 = 10’ 的
查询,Hive会加载整张表或分区,然后处理所有的rows,但是如果在字段col1上面存在索引时,那
么只会加载和处理文件的一部分。
构建数据时,Hive会根据索引字段的值构建索引信息,将索引信息存储在索引表中
查询数据时,Hive会根据索引字段查询索引表,根据索引表的位置信息读取对应的文件数据。
下面我们来实现索引的构建,例如:
当前有一张分区表tb_login_part,默认查询数据时,是没有索引的,当查询登陆日期时可以通
过分区过滤来提高效率,但是如果想按照用户ID进行查询,就无法使用分区进行过滤,只能全表扫
描数据。
如果我们需要经常按照用户ID查询,那么性能就会相对较差,我们可以基于用户ID构建索引来
加快查询效率。
⚫ 可以使用Hive3.0以下版本测试
⚫ 创建索引
--为表中的 userid 构建索引 create index idx_user_id_login on table tb_login_part(userid) --索引类型为 Compact,Hive 支持 Compact 和 Bitmap 类型,存储的索引内容不同 as 'COMPACT' --延迟构建索引 with deferred rebuild;
⚫ 构建索引
alter index idx_user_id_login ON tb_login_part rebuild;
通过运行一个MapReduce程序来构建索引
⚫ 查看索引
desc default__tb_login_part_idx_user_id_login__;
select * from default__tb_login_part_idx_user_id_login__;
索引中记录了每个用户ID对应的文件以及在文件中的位置
⚫ 删除索引
DROP INDEX idx_user_id_login ON tb_login_part;
⚫ 问题
Hive构建索引的过程是通过一个MapReduce程序来实现的,这就导致了Hive的一个
问题,每次Hive中原始数据表的数据发生更新时,索引表不会自动更新,必须手动执行
一个Alter index命令来实现通过MapReduce更新索引表,导致整体性能较差,维护相对
繁琐。例如:
◼ 表中数据发生新增或者修改
◼ 索引表没有更新
◼ 手动更新索引表
alter index idx_user_id_login ON tb_login_part rebuild;
⚫ 应用
由于Hive的索引设计过于繁琐,所以从Hive3.0版本开始,取消了对Hive Index的支
持及使用,不过如果使用的是Hive1.x或者Hive2.x在特定的场景下依旧可以使用Hive
Index来提高性能。
实际工作场景中,一般不推荐使用Hive Index,推荐使用ORC文件格式中的索引来代替Hive Index提高查询性能。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)