持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第5天,点击查看活动详情
一、Hive数据库相关操作
这个数据的结构信息是存在在DBS表结构中
hive的数据都是存储在hdfs上,那这里的default数据库在HDFS上是如何体现的?
在 hive-site.xml 中有一个参数 hive.metastore.warehouse.dir
<property>
<name>hive.metastore.warehouse.dir</name>
<value>/user/hive/warehouse</value>
<description>location of default database for the warehouse</description>
</property>
http://192.168.234.100:9870/explorer.html#/user/hive/warehouse
1.1、创建新的数据库
create database test;
二、Hive中表的操作
注意了:表中的数据是存储在hdfs中的,但是表的名称、字段信息是存储在metastore中的。这个表的结构信息是存在在TBLS表结构中,列的信息存储在COLUMNS_V2表结构中
-
创建表
create table t1(id int);
-
查看表信息
show tables;
-
查看表结构
desc t1;
-
查看表的创建信息
show create table t1;
-
修改表名
alter table t1 rename to t1_bak;
-
加载数据
在root目录下创建文件hivedata.data。里面的内容1 2 3 4
load data local inpath '/root/hivedata.data' into table t1;
查询加载的数据
-
表增加字段
alter table t1 add columns(name string);
-
表增加注释
create table t2(age int comment '年龄') comment '测试';
此时发现,注释都是乱码的。因为hive数据库里面的表都是latin1编码的,中文本来就会显示乱码,但是又不能修改
整个数据库里面所有表的编码,否则在使用hive的时候会出问题,那么只有考虑把存储字段注释和表注释相关的表的编码改为utf8。
CREATE TABLE `COLUMNS_V2` ( `CD_ID` bigint(20) NOT NULL, `COMMENT` varchar(256) CHARACTER SET latin1 COLLATE latin1_bin DEFAULT NULL, `COLUMN_NAME` varchar(767) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL, `TYPE_NAME` mediumtext, `INTEGER_IDX` int(11) NOT NULL, PRIMARY KEY (`CD_ID`,`COLUMN_NAME`), KEY `COLUMNS_V2_N49` (`CD_ID`), CONSTRAINT `COLUMNS_V2_FK1` FOREIGN KEY (`CD_ID`) REFERENCES `CDS` (`CD_ID`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1其实只要修改表COLUMNS_V2和TABLE_PARAMS这两张表的编码。
alter table COLUMNS_V2 modify column COMMENT varchar(256) character set utf8;
alter table TABLE_PARAMS modify column PARAM_VALUE varchar(4000) character set utf8;
-
hive的加载数据的分隔符
hive是有默认的分隔符的,默认的行分隔符是 '\n' ,就是换行符,而默认的列分隔符呢,是 \001 。 \001 这个是ASCII码中的特殊不常使用的不可见字符,在文本中我们可以通过 ctrl+v 和 ctrl+a 来输入 \001。
其实我们在创建表的时候指定一下分隔符就可以了。
create table t3_new( id int comment 'ID', stu_name string comment 'name', stu_birthday date comment 'birthday', online boolean comment 'is online' )row format delimited fields terminated by '\t' lines terminated by '\n';
三、Hive的数据类型
hive作为一个类似数据的框架,也有自己的数据类型,便于存储、统计、分析。hive中主要包含两大数据类型
-
基本数据类型
int、string、boolean、double等
-
复合数据类型
array、map、struct等
3.1、基本数据类型
| 数据类型 | 开始支持版本 |
|---|---|
| tinyint | ~ |
| smallint | ~ |
| int/integer | ~ |
| bigint | ~ |
| float | ~ |
| double | ~ |
| decimal | ~ |
| timestamp | 0.8.0 |
| date | 0.12.0 |
| string | ~ |
| varchar | 0.12.0 |
| char | 0.13.0 |
| boolean | ~ |
3.2、复合数据类型
| 数据类型 | 开始支持版本 | 格式 |
|---|---|---|
| array | 0.14.0 | array<data_type> |
| map | 0.14.0 | map<primitive_type,data_type> |
| struct | ~ | struct<col_name:data_type, ...> |
3.3、复合数据类型Array的应用
程序员有多个编程技能,比如会html、css、java等。每个程序员的技术栈是不一样的,如果每个技能栈都在表里面增加一列,这样是不合适的,后期学了新的技能,就需要新增列存储新的技能树了。
如果我们把技能树都拼接成一个字符串保存到一个字段中,这样针对存储层面来说没有问题,但是后期需要根据新增技能树而修改字段,这样操作起来不方便。这时候,在Hive中,可以使用Array来存储。数组中的元素怎么分割呢?通过 collection items terminated by ',' 指定的
create table coder(id int,
name string,
skill array<string>)
ROW FORMAT DELIMITED FIELDS TERMINATED BY ','
collection items terminated by '|';
测试文件数据:
1,zhangsan,html|css|vue
2,lisi,java|c#
进行加载数据
load data local inpath '/root/data/coder.data' overwrite into table coder;
查询数组中的某一个元素,使用arrayName[index]
select id,name,skill[0] from coder;
3.4、复合数据类型Map的应用
map集合里面存储的是键值对,每个键值对属于map集合的一个item。比如:有两个学生zhangsan、lisi,每个学生有语文、数学、英语,成绩如下:
1,zhangsan,chinese:80|math:90|english:100
2,lisi,chinese:89|english:70|math:88
create table student(id int,
name string,
subjects map<string,int>)
ROW FORMAT DELIMITED FIELDS TERMINATED BY ','
collection items terminated by '|'
map keys terminated by ':';
load data local inpath '/root/data/stu.data' overwrite into table student;
查询所有学生的语文和数学成绩
select id,name,subjects['chinese'],subjects['math'] from student;
3.5、复合数据类型Struct的应用
复合数据类型有点像Java中的对象
create table stu2(id int,
name string,
address struct<home_addr:string,office_addr:string>)
ROW FORMAT DELIMITED FIELDS TERMINATED BY ','
collection items terminated by '|'
lines terminated by '\n';
测试数据
1,zhangsan,bj|sh
2,lisi,sz|yz
加载数据
load data local inpath '/root/data/stu2.data' into table stu2;
单独查询数据;
select id,name,address.home_addr from stu2;
3.6、Struct和Map的区别
| Map | Struct |
|---|---|
| 可以随意增加k-v对的个数 | k-v个数是固定的 |
| 在建表语句中需要指定k-v的类型 | 建表语句中需要指定好所有的属性名称和类型 |
| 通过[]取值 | 通过.(点)取值,类似java中的对象属性引用 |
| 源数据中需要带有k-v | 源数据中只需要有v即可 |
总体而言还是map比较灵活,但是会额外占用磁盘空间,因为他比struct多存储了数据的key。struct只需要存储value,比较节省空间,但是灵活性有限,后期无法动态增加k-v
3.7、思考题
在mysql中有一张表student(id,name),还有一张表address(stu_id,home,school),还有联系方式表(stu_id,mine,others)。如果把这三张表迁移到hive中,如何迁移?
-
一对一的迁移,mysql中有几张表,hive中就创建几张表
优点:迁移成本低,包括DDL和业务逻辑,几乎不需要修改,可以直接使用
缺点:产生大量的表连接,造成查询慢
-
一对多,mysql中的多张关联表可以创建为hive中的一张表
优点:减少表连接操作
缺点:迁移成本高,需要修改原有的业务逻辑
四、Hive中的表类型
- 内部表
- 外部表
- 分区表
- 桶表
4.1、内部表
hive中的默认表类型,表数据默认存储在warehouse目录中。在加载数据的过程中,实际数据会被移动到warehouse目录中。删除表时,表中的数据和元数据将会被同时删除
4.2、外部表
建表语句中包含External的表叫做外部表。外部表在加载数据的时候,实际数据并不会移动到warehouse目录中,只是与外部数据建立一个链接(映射关系)。当删除一个外部表时,只删除元数据,不删除表中的数据,仅删除表和数据之间的链接
create external table external_table(
key string
)location '/data/external';
hdfs查看创建的目录
hdfs dfs -ls /data/external
内部表与外部表可以互相转换
-
内部表转外部表
alter table 表名 set tblproperties (‘external’=‘true’);
-
外部表转内部表
alter table 表名 set tblproperties (‘external’=‘false’);
在实际工作中,我们在hive中创建的表95%以上的都是外部表。大致工作流程如下:
- 先通过Flume采集数据,把数据上传到hdfs
- 然后在hive中创建外部表和hdfs上的数据绑定关系
- 这样就可以使用sql查询数据了。省略了load数据这步。因为先有数据,才创建的表
4.3、创建内部分区表
web服务器每天都产生一个日志数据文件,Flume把数据采集到HDFS中,每一天的数据存储到一个日期目录中,我们如果想查询某一天的数据的话,hive执行的时候默认会对所有文件扫描一遍,然后再过滤出来我们想要查询的那一天的数据(效率很低)
所有我们可以在让hive查询的时候,根据你要查询的日期,直接定位到对应的日期目录,这样就可以直接查询满足条件的数据了,效率进行了质的提升
此时我们就可以使用分区表了。分区可以理解为分类,通过分区把不同类型的数据放到不同目录中。
分区的标准就是指定分区字段,分区字段可以有一个或者多个,根据咱们刚才举得例子,分区字段就是日期
创建分区表
使用partitioned by指定区分字段
create table partition_demo(id int,
name string)
partitioned by (create_date string)
ROW FORMAT DELIMITED FIELDS TERMINATED BY ','
lines terminated by '\n'
数据格式:
1,张三
2,lisi
向分区表中加载数据(注意:在这里添加数据需要指定分区信息)
load data local inpath '/root/data/partition.data' into table partition_demo partition(create_date='20221016');
在hdfs存储
-
手动在表中添加分区
alter table partition_demo add partition(create_date='20221017');
此时hdfs中多了一个目录,只不过这个分区目录中没有数据
-
删除分区
alter table partition_demo drop partition(create_date='20221017');
根据分区条件查询数据
select * from partition_demo where create_date='20221016';
4.4、创建外部分区表
create external table ex_par(id int,
name string)
partitioned by (create_date string)
ROW FORMAT DELIMITED FIELDS TERMINATED BY ','
lines terminated by '\n'
location '/data/ex_par';
加载数据
load data local inpath '/root/data/partition.data' into table ex_par partition(create_date='20221016');
外部分区表删除,只会删除元数据信息,不会删除hdfs里面的数据
绑定数据
实际工作中,我们把数据上传到了hdfs,如何进行绑定关系呢?
alter table ex_par add partition(create_date='20221016') location '/data/ex_par/create_date=20221016'
load data命令
load data…partition这条命令做了两件事情
-
上传数据
hdfs dfs -mkdir /data/ex_par/create_date=20221016
hdfs dfs -put 源数据目录 /data/ex_par/create_date=20221016(新的数据目录)
-
添加分区(绑定数据和分区之间的关系)
alter table ex_par add partition(create_date='20221016') location '/data/ex_par/create_date=20221016'
五、桶表
桶表是对数据进行哈希取值,然后放到不同文件中存储。物理上,每个桶就是表(或者分区)里的一个文件
create table bucket_tb(id int) clustered by (id) into 4 buckets;
这个时候往桶中加载数据的时候,就不能使用load data的方式了,而是需要使用其他表中的数据,那么给桶表加载数据的写法就有了新的变化了。类似的写法:
insert into table ... select..from...;
注意:在插入数据之前需要先设置开启桶操作,不然数据无法分到不同的桶里面。其实这里的分桶就是设置reduce任务的数量,因为你分了多少个桶,最终结果就会产生多个文件,最终结果文件的数量就和reduce任务的数量是挂钩的。设置完 set hive.enforce.bucketing = true 可以自动控制reduce的数量从而适配bucket的个数
创建数据bucket.data
1
2
3
4
5
6
创建普通的内部表
create table bucket_source(id int);
往普通的内部表里加载数据
load data local inpath '/root/data/bucket.data' into table bucket_source;
把bucket_source的数据加载到桶表里面
insert into table bucket_tb select id from bucket_source where id is not null;
上面语句会生成mapreduce任务
hive> insert into table bucket_tb select id from bucket_source where id is not null;
Query ID = root_20221016160146_99ffa73a-75f7-4337-b161-f1a9b3e3acab
Total jobs = 2
Launching Job 1 out of 2
Number of reduce tasks determined at compile time: 4
In order to change the average load for a reducer (in bytes):
set hive.exec.reducers.bytes.per.reducer=<number>
In order to limit the maximum number of reducers:
set hive.exec.reducers.max=<number>
In order to set a constant number of reducers:
set mapreduce.job.reduces=<number>
Starting Job = job_1665893448730_0001, Tracking URL = http://bigdata01:8088/proxy/application_1665893448730_0001/
Kill Command = /root/software/hadoop-3.3.4/bin/mapred job -kill job_1665893448730_0001
Hadoop job information for Stage-1: number of mappers: 1; number of reducers: 4
2022-10-16 16:03:06,998 Stage-1 map = 0%, reduce = 0%
2022-10-16 16:03:45,958 Stage-1 map = 100%, reduce = 0%, Cumulative CPU 3.51 sec
2022-10-16 16:04:34,686 Stage-1 map = 100%, reduce = 50%, Cumulative CPU 10.96 sec
2022-10-16 16:04:35,722 Stage-1 map = 100%, reduce = 100%, Cumulative CPU 19.02 sec
MapReduce Total cumulative CPU time: 19 seconds 20 msec
Ended Job = job_1665893448730_0001
Loading data to table default.bucket_tb
Launching Job 2 out of 2
Number of reduce tasks determined at compile time: 1
In order to change the average load for a reducer (in bytes):
set hive.exec.reducers.bytes.per.reducer=<number>
In order to limit the maximum number of reducers:
set hive.exec.reducers.max=<number>
In order to set a constant number of reducers:
set mapreduce.job.reduces=<number>
Starting Job = job_1665893448730_0002, Tracking URL = http://bigdata01:8088/proxy/application_1665893448730_0002/
Kill Command = /root/software/hadoop-3.3.4/bin/mapred job -kill job_1665893448730_0002
Hadoop job information for Stage-3: number of mappers: 1; number of reducers: 1
2022-10-16 16:06:04,508 Stage-3 map = 0%, reduce = 0%
2022-10-16 16:06:29,015 Stage-3 map = 100%, reduce = 0%, Cumulative CPU 2.08 sec
2022-10-16 16:07:02,718 Stage-3 map = 100%, reduce = 100%, Cumulative CPU 4.83 sec
MapReduce Total cumulative CPU time: 4 seconds 830 msec
Ended Job = job_1665893448730_0002
MapReduce Jobs Launched:
Stage-Stage-1: Map: 1 Reduce: 4 Cumulative CPU: 19.02 sec HDFS Read: 31658 HDFS Write: 864 SUCCESS
Stage-Stage-3: Map: 1 Reduce: 1 Cumulative CPU: 4.83 sec HDFS Read: 9848 HDFS Write: 157 SUCCESS
Total MapReduce CPU Time Spent: 23 seconds 850 msec
OK
Time taken: 338.091 seconds
此时我们可以去hdfs里面查看4个文件
此时我们可以看到是4个文件。
hdfs dfs -cat /user/hive/warehouse/bucket_tb/000000_0
桶表的作用
-
数据抽样
假如我们使用的是一个大规模的数据集,我们只想去抽取部分数据进行查看。使用bucket表可以变得更加的高效
select * from bucket_tb tablesample(bucket 1 out of 4 on id);
tablesample是抽样语句 语法解析:
TABLESAMPLE(BUCKET x OUT OF y ON column)
- y尽可能是桶表的bucket数的倍数或者因子
- y必须要大于等于x y表示是把桶表中的数据随机分为多少桶 x表示取出第几桶的数据
-
提高查询效率
join查询,可以避免产生笛卡尔积的操作
select a.id,a.name,b.addr from a join b on a.id = b.id;
如果a表和b表已经是分桶表,而且分桶的字段是id字段,那么做这个操作的时候就不需要再进行全表笛卡尔积了,因为分桶之后相同规则的id已经在相同的文件里面了。
六、视图
视图就是一张虚拟的表,它是对数据库的一个逻辑表示。主要作用:使用视图可以降低查询的复杂度。
6.1、创建视图
create view v1 as select id,name from ex_par;
查询视图:
select * from v1;
它在hdfs是没有存储的,它在元数据里面是这样的
删除视图:
drop view 视图名