Hive
- 知识点结构图
一、Hive概述
1.1 Hive定义
定义:Hive是由FaceBook开源,主要用于解决海量结构化日志的数据统计。它是基于Hadoop的一个数据仓库工具,可以将结构化的数据文件映射成一张表,并提供类SQL查询功能。
本质上是将HSQL转化成MR程序。(具体来说是Hive内部封装了很多MapReduce模板,通过Hive框架匹配出相应的MapReduce模板直接运行MR程序)
Hive的数据存储是在HDFS,底层实现是MapReduce,执行程序是在Yarn上。
Hive加载数据到表中时不会做数据的校验,在读取数据时才校验。
hive底层支持的执行引擎有3种
- mapreduce(默认)
- tez(支持DAG作业的计算框架)
- spark(基于内存的分布式计算框架),企业可以切换这种框架,基于内存的速度更快。
1.2Hive优缺点
- 优点:
- (1)操作接口采用类SQL语法,提供快速开发的能力(简单、容易上手)。
- (2)避免了去写MapReduce,减少开发人员的学习成本。
- (3)Hive支持用户自定义函数,用户可以根据自己的需求来实现自己的函数。
- (4)底层实现是MapReduce,适合处理大数据。
- 缺点:
- (1)Hive 不支持记录级别的删改操作,主要原因是Hive在HDFS中存储,进行删除是物理删除,代价比较高,所以只支持覆盖和追加
- (2)Hive 的查询延时很严重(主要浪费在MR程序的资源调度,任务计算)
- (3)HSQL的表达能力有限:
- Hive自动生成的MapReduce作业,通常情况下不够智能化
- 数据挖掘方面不擅长,由于MapReduce数据处理流程的限制,效率更高的算法却无法实现。
- (4)Hive调优比较困难,粒度较粗
二、Hive架构原理
2.1 架构解释
- 1、用户接口:Client
- CLI(hive shell)、JDBC/ODBC(java访问hive)、WEBUI(浏览器访问hive)
- 2、元数据:Metastore
-
元数据包括:表名、表所属的数据库(默认是default)、表的拥有者、列/分区字段、表的类型(是否是外部表)、表的数据所在目录等;
- 默认存储在自带的derby数据库中,有安装MySQL就可存储在Mysql中
-
- 3、Hadoop集群
- 使用HDFS进行存储,使用MapReduce进行计算。
- 4、Driver:驱动器
- 解析器(SQL Parser) 对SQL进行语法分析和语义分析。
- 将SQL字符串转换成抽象语法树AST
- 对AST进行语法分析,比如表是否存在、字段是否存在、SQL语义是否有误。
- 编译器(Physical Plan):将AST编译生成逻辑执行计划。
- 优化器(Query Optimizer):对逻辑执行计划进行优化。
- 执行器(Execution):把逻辑执行计划转换成可以运行的物理计划。对于Hive来说默认就是mapreduce任务
- 解析器(SQL Parser) 对SQL进行语法分析和语义分析。
2.2 Hive的工作原理(参考上面的架构图)
Hive首先是一个客户端工具,它提供了一些用户可操作的接口,可以通过交互shell,JDBC和web UI方式连接Hive,在Hive的内部有个Driver驱动器,驱动器里面实现了解析器,编译器,优化器和执行器的功能,在用Hsql查询表时,sql语句在驱动器中会先做语法和语义解析,解析之后再进行相应的语法编译生成逻辑执行计划,然后在通过优化器时对逻辑执行计划进行优化,最后在执行器中转换成对应的mr jar包,打包给hadoop集群运行获得结果。
总结:Hive 通过给用户提供的一系列交互接口,接收到用户的指令(SQL),使用自己的驱动器 Driver, 结合元数据(MetaStore),将这些指令翻译成 MapReduce,提交到 Hadoop 中执行,最后,将执行返回的结果输出到用户交互接口。
三、Hive与其他数据库比较
3.1 查询语言
Hive使用的是类SQL的查询语言,与标准SQL还是有一定区别。
3.2 数据存储位置
Hive的数据存储在HDFS中,而数据库则将数据保存在块设备或者本地文件系统中。
3.3 数据更新
Hive 不支持记录级别的删改操作,主要原因是Hive在HDFS中存储,进行删除是物理删除,代价比较高,所以只支持覆盖和追加。
而数据库中的数据通常是需要经常修改的,支持数据更新操作。
3.4 索引
Hive在加载数据的过程中不会对数据进行任何处理,包括不会对数据中的某些key建立索引,在访问某些特定数据时,是扫描全表的数据,因此访问延迟较高。但hive底层是MR程序,可以并行访问数据,所以对于大数据量的情况下,hive查询仍然有优势。
而数据库通常会针对一个或几个列建立索引,对于一些特定条件的查询具有很高的查询效率。
3.5 执行和执行延迟
Hive 中大多数查询的执行是通过 Hadoop 提供的 MapReduce 来实现的。而数据库通常有自己的执行引擎。
前面说过hive没有索引,查询时是扫描整张表,所以延迟较高,另一个导致hive延迟高的原因在于底层是MR程序,时间都浪费在mr的资源调度(yarn分配资源)和任务计算中(map和reduce过程)。
而数据库的执行延迟较低,但只适用数据规模较小的情况,当数据规模大到超过数据库的处理能力的时候,Hive 的并行计算显然能体现出优势。
3.6 可扩展性
Hive的扩展性和Hadoop一样好,可以增加机器提高性能。
数据库由于ACID语义的限制,一般扩展很有限。
3.7 数据规模
Hive可以支持很大规模的数据;对应的,数据库支持的数据规模较小。
四、Hive安装
– 待补充
五、Hive常用命令
5.1 常用命令
# 退出
hive(default)>exit;
# 切换数据库
hive> use 数据库名;
# 查看数据库信息
hive> desc databases 数据库名;
# 显示数据库
hive> show databases;
# 过滤显示查询的数据库
hive> show databases like 'db_hive*';
# 查看系统自带的函数
hive> show functions;
# 然后显示自带的函数的用法
hive> desc function 函数名
# 查询表类型,可以查出很多表结构信息
hive>desc formatted 表名;
# 查看分区表有多少分区,注意加s
hive> show partitions 表名;
# 重命名表
hive> ALTER TABLE table_name RENAME TO new_table_name
# 更新列
ALTER TABLE table_name CHANGE [COLUMN] col_old_name col_new_name column_type [COMMENT col_comment] [FIRST|AFTER column_name];
# 添加列
alter table dept_partition add columns(deptdesc string);
# 替换列???
alter table student replace columns(deptno string,dname string,loc string); --替换表中所有的字段。
# 删除表
drop table dept_partition;
5.2 常用交互命令
前提是先启动Hadoop集群和mysql服务,并配置好hive元数据到mysql。
交互方式有三种:Hive交互Shell,Hive JDBC服务和Hive的命令。
5.2.1 Hive交互shell
通过输入在hive中的/bin目录下的hive命令,进入hive,直接输出sql查询.
$ cd
$ bin/hive
mysql> show databases;
+--------------------+ | Database | +--------------------+
| information_schema |
| metastore |
| mysql |
| performance_schema |
| test |
+--------------------+
5.2.2 Hive JDBC服务
-
启动hiveserver2服务
- 前台启动
bin/hive --service hiveserver2
- 后台启动
nohup bin/hive --service hiveserver2 &
- 然后 启动beeline
$ bin/beeline Beeline version 1.2.1 by Apache Hive beeline> # netstat nlp命令可以查看当前启动了哪些服务和对应的端口,用于下面连接用的端口10000
-
最后beeline连接hiveserver2
beeline> !connect jdbc:hive2://hadoop102:10000(回车)
5.2.3 Hive的命令方式
- “-e”不进入 hive 的交互窗口执行 sql 语句
cd /opt/bigdata/hive
$ bin/hive -e "select id from student;"
- “-f”执行脚本中 sql 语句
$ bin/hive -f /opt/module/datas/hivef.sql
5.3 其他命令操作
5.3.1 在 hive cli 命令窗口中如何查看 hdfs 文件系统
hive(default)>dfs -ls /;
5.3.2 在 hive cli 命令窗口中如何查看本地文件系统
hive(default)>! ls /opt/module/datas;
5.3.3 查看在 hive 中输入的所有历史命令
(1)进入到当前用户的根目录/root 或/home/atguigu
(2)查看. hivehistory 文件
[atguigu@hadoop102 ~]$ cat .hivehistory
六、Hive数据类型
6.1 基本数据类型
Hive数据类型 | 长度 | 例子 |
---|---|---|
tinyint | 1 byte有符号整数 | 20 |
smalint | 2 byte有符号整数 | 20 |
int | 4 byte有符号整数 | 20 |
bigint | 8 byte有符号整数 | 20 |
boolean | 布尔类型,true或false | true |
float | 单精度浮点数 | 3.14159 |
double | 双精度浮点数 | 3.14159 |
string | 字符串,可以指定字符集 | ‘now is the time’ |
timestamp | 时间类型 | 1563157873 |
date | 日期 | 20200829 |
binary | 字节数组 |
对于 Hive 的 String 类型相当于数据库的 varchar 类型,该类型是一个可变的字符串,不过它不能声明其中最多能存储多少个字符,理论上它可以存储 2GB 的字符数。
6.2 集合数据类型
Hive 有三种复杂数据类型 ARRAY、MAP 和 STRUCT。ARRAY 和 MAP 与 Java 中的Array 和 Map 类似,而 STRUCT 与 C 语言中的 Struct 类似,它封装了一个命名字段集合。复杂数据类型允许任意层次的嵌套。
类型名称 | 描述 | 举例 |
---|---|---|
array | 一组有序的字段,字段类型必须相同 array(元素1,元素2) | Array(1,2,3) |
map | 一组无序的键值对 map(k1,v1,k2,v2) | Map(‘a’:1,‘b’:2) |
struct | 一组命名的字段,字段类型可以不同 struct(元素1,元素2) | Struct(‘a’,1,2,0) |
-
array字段的元素访问方式:下标获取元素,下标从0开始
-
获取第一个元素
- array[0]
-
-
map字段的元素访问方式:通过键获取值
-
获取a这个key对应的value
- map[‘a’]
#用法 select map_key['a'] from user;
-
-
struct字段的元素获取方式:定义一个字段c的类型为struct{a int;b string},使用c.a 和c.b 获取其中的元素值
- 这里可以把这种类型看成是一个对象
示例:
# 建表:
create table test
( name string,
friends array<string>,
children map<string, int>, --注意这里指定数据类型的方式
address struct<street:string, city:string> ) --注意这里指定数据类型的方式
row format delimited fields terminated by ',' --列分隔符
collection items terminated by '_' --指定MAP STRUCT 和 ARRAY ,他们自身数据的分隔符(数据分割 符号)
map keys terminated by ':' ---- MAP 中的 key 与 value 的分隔符
lines terminated by '\n'; --- 行分隔符
# 测试数据:
songsong,bingbing_lili,xiao song:18_xiaoxiao song:19,hui long guan_beijing yangyang,caicai_susu,xiao yang:18_xiaoxiao yang:19,chao yang_beijing
#导入数据后查询
hive (default)> select friends[1],children['xiao song'],address.city from test where name="songsong";
OK
_c0 _c1 city
lili 18 beijing
Time taken: 0.076 seconds, Fetched: 1 row(s)
6.3 类型转换
主要有两种,一种是隐式转换,小的会自动转换成大的,另一种是用cast函数手动转换
隐式类型转换
-
系统自动实现类型转换,不需要用户干预
-
如tinyint可以转换成int,int可以转换成bigint。
-
所有整数类型、float 和 string类型都可以隐式地转换成double。
-
tinyint、smallint、int都可以转换为float。
-
boolean类型不可以转换为任何其它的类型。
-
手动类型转换
-
可以使用cast函数操作显示进行数据类型转换
- cast (‘1’ as int)将把字符串’1’ 转换成整数1;
- 如果强制类型转换失败,如执行cast(‘x’ as int),表达式返回空值 NULL。
七、Hive需注意的DDL语句
7.1 数据库
- 创建一个数据库,指定数据库在 HDFS 上存放的位置
如果不指定,默认数据库存放的位置是/user/hive/warehouse/*.db。
create database if not exists 数据库名 location '/db_hive2.db';
- 显示数据库信息
hive> desc database 数据库名;
- 显示数据库详细信息
hive> desc database extended 数据库名;
- 切换数据库
hive> use 数据库名;
- 修改数据库:用户可以使用 ALTER DATABASE 命令为某个数据库的 DBPROPERTIES 设置键-值对属性值,来描述这个数据库的属性信息,但数据库的其他元数据信息都是不可更改的,包括数据库名和数据库所在的目录位置。
hive> alter database 数据库名 set dbproperties('createtime'='20170830');
-
删除数据库
如果数据库不存在,最好用if exists判断一下。
另外也可以用cascade强制删除数据库
#删除数据库
hive>drop database if exists 数据库名
# 强制删除数据库
hive>drop database 数据库名 cascade;
7.2 建表语法
CREATE [EXTERNAL] TABLE [IF NOT EXISTS] table_name
[(col_name data_type [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], ...)] INTO num_buckets BUCKETS]
[ROW FORMAT row_format] row format delimited fields terminated by “分隔符”
[collection items terminated by ':'] (指定struct数据类型内的分隔符)
[map keys terminated by ':'] (指定map键值对的分隔符)
[STORED AS file_format]
[LOCATION hdfs_path]
- EXTERNAL 指定一个外部表
- PARTITIONED BY 创建分区表
- CLUSTERED BY 创建分桶
- SORTED BY 按照字段排序(一般不常用)
- ROW FORMAT 指定每一行中字段的分隔符
- 比如:row format delimited fields terminated by ‘\t’
- STORED AS 指定存储文件类型,
- 常用的存储文件类型:SEQUENCEFILE(二进制序列文件)、TEXTFILE(文本)、RCFILE(列式存储格式文件)
- 如果文件数据是纯文本,可以使用STORED AS TEXTFILE。如果数据需要压缩,使用 STORED AS SEQUENCEFILE。
- LOCATION 指定表在HDFS上的存储位置。值得一提的是,假如建表后指定了location,后期直接往这个地址存储sql格式的数据,那么对应的表也是能查到这部分新增的数据的,因为这里建表定义的location实际上就是指向某个文件地址。
7.3 创建内部表和外部表
7.3.1 创建内部表-不加external
建表语句不加external就是内部表。
有三种常用建表方法:
- 第一种,直接建表
create table if not exists student(
id int,
name string
)
row format delimited fields terminated by '\t' (如果不指定,整行数据会被当做一个字段)
stored as textfile;(不指定就默认是文本文件)
- 第二种,查询建表法
#通过AS语句,将查询的子结果存在新表里
create table if not exists student1 as select id, name from student;
- 第三种,Like建表法
create table if not exists student2 like student;
7.3.2 创建外部表
create external table if not exists default.emp(
id int,
name string,
age int
)
row format delimited fields terminated by '\t'
location '/hive/bigdata' (这个目录可以是不存在的,在建表时创建)
- 创建外部表的时候需要加上external 关键字
- location字段可以指定,也可以不指定
- 指定就是数据存放的具体目录
- 不指定就是使用默认目录 /user/hive/warehouse
7.4 内部表和外部表的区别
- 1.创建表时:创建内部表时,会将数据移动到数据仓库指向的路径;创建外部表时需要加上external关键字,它仅记录数据所在的路径,不对数据的位置做任何改变
- 2.删除表时:删除表后,内部表的元数据和真实数据会被一起删除,而外部表仅删除元数据,不删除真实数据,这样外部表相对来说更加安全些,数据组织也比较灵活,方便共享原始数据。(直接重建原来的表后,数据就自动导入到原来的表去了,location直接指向原来存储的位置.)
- 外部表保障底层数据的安全性,内部表适用于管理中间表和结果表。
内部表和外部表的互相转换:
#内部表改为外部表
alter table 表名 set tblproperties('EXTERNAL'='TRUE');
#外部表改为内部表
alter table 表名 set tblproperties('EXTERNAL'='FALSE');
7.5 Hive的分区表
分区表实际上就是对应HDFS文件系统上的独立的文件夹。
Hive分区就是分目录,把表的数据分目录存储,存储在不同的文件夹下,后期按照不同的目录查询数据,不需要进行全量扫描,就可以提升查询效率。
分区的层级没有上限,一般是3层。
7.5.1 分区表的基本操作
# 创建分区表语法
hive> create table dept_partition
( deptno int, dname string, loc string )
partitioned by (month string)
row format delimited fields terminated by '\t';
# 加载数据到分区表中
hive> load data local inpath '/opt/module/datas/dept.txt' into table default.dept_partition partition(month='201709');
# 查询分区表中数据
hive> select * from dept_partition where month='201709';
#增加分区
hive> alter table dept_partition add partition(month='201706') ;
# 同时创建多个分区
hive> alter table dept_partition add partition(month='201705') partition(month='201704');
# 删除分区
hive> alter table dept_partition drop partition (month='201704');
# 同时删除多个分区
hive> alter table dept_partition drop partition (month='201705'), partition (month='201706');
7.5.2 创建二级分区
# 创建二级分区表
hive> create table dept_partition2
( deptno int, dname string, loc string )
partitioned by (month string, day string)
row format delimited fields terminated by '\t';
# 加载数据到二级分区表中
hive> load data local inpath '/opt/module/datas/dept.txt' into table default.dept_partition2 partition(month='201709', day='13');
#也可以在HDFS创建这张表对应分区目录的存放位置,然后把数据放到该分区目录下,再创建分区时自动关联上这部分数据。
hive> dfs -mkdir -p /user/hive/warehouse/dept_partition2/month=201709/day=11;
hive> dfs -put /opt/module/datas/dept.txt /user/hive/warehouse/dept_partition2/month=201709/day=11;
hive> alter table dept_partition2 add partition(month='201709', day='11');
7.5.3 Hive的静态分区
insert 插入语句时,表的分区字段的值需要开发人员手动指定具体的值
-
创建分区表
create table order_partition(order_number string, order_price double, order_time string) partitioned by(month string) row format delimited fields terminated by '\t';
-
导出数据到分区表
load data local inpath '/opt/bigdata/order_created.txt' overwide into table order_partition partition(month='20200401');
7.5.4 Hive的动态分区
按照需求实现把数据自动导入到表的不同分区中,不需要手动指定。
注意要先开启动态分区的参数,以及分区字段要放在最后面。
#创建表语句没有变
create table order_partition(order_number string,
order_price double,
order_time string)
partitioned by(month string)
row format delimited fields terminated by '\t';
#不同的是导入时动态指定分区值
#首先需要设置动态分区的参数
hive>set hive.exec.dynamic.partition=true;//使用动态分区
hive>set hive.exec.dynamic.partition.mode=nonstrict //非严格模式
#然后导入数据,注意这里字段查询的顺序,分区字段一定要放在最后,否则数据有问题
insert into table order_partition partition(month) select order_number,order_price,order_time,month from 源表;
#最后可以查看分区,注意partition加s
show partitions order_partition;
7.6 Hive的分桶表
分桶是将整个数据内容按照某列属性值去hash值进行区分,对取得的hash再做模运算(columnValue.hashCode % 桶数),具有相同结果的数据进入同一个文件中。(本质上来说可以看成是对一个大文件进行拆分成小文件)
比如将name列分为4个桶,则name属性的值取hash值后对4求模运算,取模结果0,1,2,3的分开存放到不同文件中。
可以将Hive中的分桶理解成MapReduce中的HashPartitioner的原理。
7.6.1 作用
-
取样更高效,可以看下哪些值出现频率比较高,避免数据倾斜等。
抽样查询桶表的语句:tablesample(bucket x out of y on column)
x 表示从第几个桶开始取数
y 表示每隔几个桶取一个分桶
select * from user_buckets_demo tablesample(bucket 1 out of 2 on id); --假设建表时分了4个桶。 --表示从第一个桶开始抽样,每隔2个桶取一个桶抽样,第二个桶是1+2 = 3 --需要抽样的桶数 : 4/2 = 2个
-
提升某些查询操作效率,比如map-side join(在对列分桶时,已经将相同值聚集在一起了,所以能提高查询效率)
7.6.2 案例
create table user_buckets_demo(id int,name string)
clustered by(id) into 4 buckets
row format delimited fields terminated by '\t';
# 注意,建完分桶表之后,要开启一下参数,否则实际hdfs上的文件是不会被拆分的。
hive (default)> set hive.enforce.bucketing=true;
hive (default)> set mapreduce.job.reduces=-1;
#加载数据到分桶表
#分桶表不能直接load加载数据,需要从其它表查询插入
Insert into table user_buckets_demo select * from user_demo;
7.6.3 分桶表和分区表的区别
- 在建表语句中,分桶表必须是建表中已有的字段,而分区表必须是建表中没有的字段。
- 分区有动态分区和静态分区,而分桶没有,但分桶的数量可以固定。
- 分区针对的是数据的存储路径;分桶针对的是数据文件。
- 分区就是分目录,把表的数据分目录存储,存储在不同的文件夹下,后期按照不同的目录查询数据,不需要进行全量扫描,就可以提升查询效率;而分桶是对原始数据的某个字段取哈希值求模运算后,将相同结果存入同一文件夹,相当于把大文件拆分成多个小文件,由于相同值已经聚集在一起,更适合用于map-side join和抽样取数。
八、Hive需注意的DML语句
8.1 Hive数据的导入
8.1.1 向表中装载数据(Load)
语法:
load data [local] inpath 'dataPath' override | into table student [partition 分区值];
load data:表示加载数据
local:表示从本地加载数据到表,如果忽略不写则表示从HDFS加载数据
inpath:表示加载数据的路径
override:表示覆盖表中已有的数据,需要注意使用
table:表示加载到哪张表
partition:表示上传到制定分区
#例如:
load data local inpath '/opt/bigdata/person.txt' into table person partition(dt='20200401');
8.1.2 通过查询语句向表中插入数据(Insert)
- 从指定的表中查询数据结果然后插入目标表中
insert into | override table student select * from XXXX;
insert into | override table student partition(dt='20200401') select * from XXXX;
8.1.3 查询语句中创建表并导入数据(AS Select)
create table if not exists student as select * from XXXX;
8.1.4 创建表时通过location指定加载数据路径
create table if not exists student (id int,
name string)
row format delimited fields terminated by '\t'
location '/user/hive/warehouse/student1';
#然后往hdfs的路径上传对应的数据student.txt
dfs -put /opt/bigdata/student.txt /user/hive/warehouse/student1
8.1.5 import数据到指定hive表中
注意先用export导出数据后,再import
create table student2 like student1;
export table student1 to '/opt/bigdata/student1';(这里是hdfs的目录)
import table student2 from '/opt/bigdata/student1';
8.2 Hive数据的导出
8.2.1 Insert 导出
- 1.将查询的结果导出到本地
insert override local directory '/opt/bigdata/student';
#默认文件分隔符会是‘\001’
#之后本地会生成一个日志型的文件
- 2.将查询的结果格式化后导出到本地
insert override local directory '/opt/bigdata/student' row format delimited fields terminated by ',';
- 3.将查询的结果导出到HDFS(注意这里没有local)
insert override directory '/export/student' row format delimited fields terminated by ',';
8.2.2 Hadoop命令导出
get命令直接下载到本地磁盘
dfs -get /usr/hive/warehouse/student/student.txt /opt/bigdata/data
8.2.3 Hive shell命令导出
有两种:
-
1.hive -e “sql语句” >> file; 这种是直接执行sql语句,把结果导出到文件中。
-
2.hive -f “sql文件” > file; 这种是执行完sql文件后,将查询结果写入到file中
bin/hive -e 'select * from default.student;' >> /opt/bigdata/student/student.txt
8.2.4 export导出到HDFS上
hive>export table student to '/usr/hive/warehouse/student';
8.3 清除表中的数据truncate
注意:Truncate 只能删除内部表,不能删除外部表中数据
hive (default)> truncate table student;
九、Hive查询注意点
9.1 连接查询
- 笛卡尔积
- 定义:
- 条件:省略连接条件、连接条件无效、所有表中的所有行互相连接
- 内连接:返回连接的两个表中与连接条件相匹配的数据
- 左外连接:返回JOIN 操作符左边表中符合 WHERE 子句的所有记录,右表不符合的显示为空。
- 右外连接:返回JOIN 操作符右边表中符合 WHERE 子句的所有记录,左表不符合的显示为空。
- 全外连接:返回所有表中符合 WHERE 语句条件的所有记录。如果任一表的指定字段没有符合条件的值的话,那么就使用 NULL 值替代。
9.2 开启本地模式
如果没有开启本地模式,那么大部分函数操作会经过yarn调度分配,速度较慢。开启后就不会提交到yarn中去,能快速返回查询结果。
限制:如果文件超过256M或者文件个数超过4个,那么系统也会自动关闭本地模式。
set hive.exec.model.local.auto=true;
9.3 排序
9.3.1 order by 全局排序
Order By:全局排序,在最后进行排序时只有一个reduce
9.3.2 sort by内部排序
Sort By:每个 Reducer 内部进行排序,对全局结果集来说不是排序。
sort by针对多个reduce中每个reduce进行排序。
- 设置reduce个数
set mapreduce.job.reduces=3;
- 查看reduce个数
set mapreduce.job.reduces;
- 将查询结果导入到文件中(按照成绩列降序排序)
insert override local directory'/opt/bigdata/sort' select * from student s sort by s.score;
#查询结果会被分成3个文件,每个文件都按成绩排序好。
9.3.3 distribute by 分区排序
Distribute By:类似于mr中的partition,采用hash算法,在map端将查询结果中hash值相同的结果分发到对应的reduce中,结合sort by使用。
注意,Hive 要求 DISTRIBUTE BY 语句要写在 SORT BY 语句之前。
字段.hashCode % reduce个数
案例:
#先设置reduce个数
set mapreduce.job.reduces=3;
#通过distribute by 进行数据的分区,将不同的sid划分到不同的reduce中去
insert override local directory '/opt/bigdata/distribute' select * from student distribute by sid sort by score;
#结果是先根据sid分到不同reduce,然后在每个reduce对成绩排序,那么如果这里reduce只有一个,其实跟全局排序效果是一样的
有一点要注意的是,distribute by和分桶的概念很像,都用到了hashcode和模运算,不同的是分桶是为了在hdfs存储时将大文件分成多个内部相关联的小文件,而distribute by 这里是针对查询的结果集进行一个排序展示。
9.3.4 cluster by
当distribute by 和sort by的字段一样时,可以用cluster by代替,效果是一样的
但是cluster by排序只能是升序排序,不能指定排序规则为 ASC 或者 DESC。
9.4 行转列
行转列主要用到这三个函数:
- CONCAT(string A/col, string B/col…):拼接字符串,只要其中一个是 NULL,那么将返回 NULL
- CONCAT_WS(separator, str1, str2,):指定分隔符进行拼接字符串,只要有一个字符串不是 NULL,就不会返回 NULL。
- COLLECT_SET(col):相当于把某个字段的多行数据编成一行,并且如果多行里面有重复值,那它结果也会去重。
案例:
现有这样一张表
需求:
SQL语句:
select t1.base,
concat_ws('|', collect_set(t1.name)) name
from(select name,
concat(constellation, ",", blood_type) base
from person_info
) t1 group by t1.base;
9.5 列转行
可以使用lateral view函数
函数说明
EXPLODE(col):将 hive 一列中复杂的 array 或者 map 结构拆分成多行。
LATERAL VIEW
用法:LATERAL VIEW udtf(expression) tableAlias AS columnAlias
解释:用于和 split, explode 等 UDTF 一起使用,它能够将一列数据拆成多行数据,在此基础上可以对拆分后的数据进行聚合。
原数据:
需求:
select movie, category_name
from movie_info
lateral view explode(category) table_tmp as category_name;
十、Hive常用函数
10.1 concat函数
concat 函数在连接字符串的时候,只要其中一个是 NULL,那么将返回 NULL
hive> select concat('a','b'); --ab
hive> select concat('a','b',null); --NULL
10.2 concat_ws 函数
concat_ws 函数在连接字符串的时候,只要有一个字符串不是 NULL,就不会返回 NULL。
concat_ws 函数需要指定分隔符。
ab可以换成数组,结果是对数组内的每一个进行拼接。
hive> select concat_ws('-','a','b'); --a-b
hive> select concat_ws('-','a','b',null); --a-b
hive> select concat_ws('','a','b',null); --ab
10.3 STR_TO_MAP 函数
也就是将字符串变成键值对。
(1)语法描述
STR_TO_MAP(VARCHAR text, VARCHAR listDelimiter, VARCHAR keyValueDelimiter)
(2)功能描述
使用 listDelimiter 将 text 分隔成 K-V 对,然后使用 keyValueDelimiter 分隔每个 K-V 对,
组装成 MAP 返回。默认 listDelimiter 为( ,),keyValueDelimiter 为(=)。
(3)案例
str_to_map('1001=2020-03-10,1002=2020-03-10', ',' , '=')
输出
{"1001":"2020-03-10","1002":"2020-03-10"}
10.4 Collect_set函数
相当于把某个字段的多行数据编成一行,并且如果多行里面有重复值,那它结果也会去重。
要和group by结合使用。
举例:
#表中有这样的数据:
hive (gmall)> select * from stud;
stud.name stud.area stud.course stud.score
zhang3 bj math 88
li4 bj math 99
wang5 sh chinese 92
zhao6 sh chinese 54
tian7 bj chinese 91
#把同一分组的不同行的数据聚合成一个集合
hive (gmall)> select course, collect_set(area), avg(score) from stud group by course;
chinese ["sh","bj"] 79.0
math ["bj"] 93.5
#用下标可以取某一个
hive (gmall)> select course, collect_set(area)[0], avg(score) from stud group by course;
chinese sh 79.0
math bj 93.5
10.5 nvl函数
NVL(表达式 1,表达式 2)
返回第一个不为空的表达式。该函 数的目的是把一个空值(null)转换成一个实际的值。其表达式的值可以是数字型、字符型 和日期型。但是表达式 1 和表达式 2 的数据类型必须为同一个类型。
10.6 日期函数
- date_format函数
相当于to_date函数
-
date_add 函数(加减日期)
-
next_day 函数
取下一个…的日期,比如
#取当前天的下一个周一
hive (gmall)> select next_day('2020-03-12','MO'); --2020-03-16
#取当前周的周一
hive (gmall)> select date_add(next_day('2020-03-12','MO'),-7);
- last_day 函数(求当月最后一天日期)
hive (gmall)> select last_day('2020-03-10'); --2020-03-31
10.7 LATERAL VIEW函数
函数说明 :
EXPLODE(col):将 hive 一列中复杂的 array 或者 map 结构拆分成多行。
LATERAL VIEW
用法:LATERAL VIEW udtf(expression) tableAlias AS columnAlias
解释:用于和 split, explode 等 UDTF 一起使用,它能够将一列数据拆成多行数据,在此基础上可以对拆分后的数据进行聚合。
原数据:
需求:
select movie, category_name
from movie_info
lateral view explode(category) table_tmp as category_name;
十一、Hive压缩和存储
11.1开启Map端输出阶段压缩
由于Hive底层是运行Mr程序,所以Hive的压缩配置和前面MapReduce的数据压缩是一样的。
开启 map 输出阶段压缩可以减少 job 中 map 和 Reduce task 间数据传输量。具体配置如下:
# 开启 hive 中间传输数据压缩功能
hive (default)>set hive.exec.compress.intermediate=true;
# 开启 mapreduce 中 map 输出压缩功能
hive (default)>set mapreduce.map.output.compress=true;
# 设置 mapreduce 中 map 输出数据的压缩方式:Snappy
hive (default)>set mapreduce.map.output.compress.codec= org.apache.hadoop.io.compress.SnappyCodec;
11.2 开启Reduce端输出阶段压缩
当 Hive 将 输 出 写 入 到 表 中 时 , 输 出 内 容 同 样 可 以 进 行 压 缩 。 属 性 hive.exec.compress.output 控制着这个功能。用户可能需要保持默认设置文件中的默认值 false,这样默认的输出就是非压缩的纯文本文件了。用户可以通过在查询语句或执行脚本中设置这个值为 true,来开启输出结果压缩功能。
# 开启 hive 最终输出数据压缩功能
hive (default)>set hive.exec.compress.output=true;
# 开启 mapreduce 最终输出数据压缩
hive (default)>set mapreduce.output.fileoutputformat.compress=true;
# 设置 mapreduce 最终数据输出压缩方式
hive (default)> set mapreduce.output.fileoutputformat.compress.codec = org.apache.hadoop.io.compress.SnappyCodec;
# 设置 mapreduce 最终数据输出压缩为块压缩
hive (default)> set mapreduce.output.fileoutputformat.compress.type=BLOCK;
11.3 文件存储格式
Hive 支持的存储数的格式主要有:TEXTFILE 、SEQUENCEFILE、ORC、PARQUET。
11.3.1 列式存储和行式存储
如图 6-10 所示左边为逻辑表,右边第一个为行式存储,第二个为列式存储。
-
1.行存储的特点
查询满足条件的一整行数据的时候,列存储则需要去每个聚集的字段找到对应的每个列的值,行存储只需要找到其中一个值,其余的值都在相邻地方,所以此时行存储查询的速度更快。(注意是一整行)
-
2.列存储的特点
因为每个字段的数据聚集存储,在查询只需要少数几个字段的时候,能大大减少读取的数据量;每个字段的数据类型一定是相同的,列式存储可以针对性的设计更好的设计压缩算法。(注意是几个字段,列式存储在读取时,都是直接读取这几列下的数据,而行式存储读取几列时,是在一行上重复找符合的字段数据)
TEXTFILE 和 SEQUENCEFILE 的存储格式都是基于行存储的;
ORC 和 PARQUET 是基于列式存储的。
11.3.2 TextFile格式
默认格式,数据不做压缩,磁盘开销大,数据解析开销大。可结合 Gzip、Bzip2 使用,但使用 Gzip 这种方式,hive 不会对数据进行切分,从而无法对数据进行并行操作。 (如果可以分割,那么单一文件可以由多个mapreduce程序处理,可以更好的并行化)
11.3.3 Orc格式
– 待补充
11.3.4 Parquet 格式
– 待补充
11.3.5 主流文件存储格式压缩对比
– 待补充
参考《Hive主流文件存储格式对比.md》 https://blog.csdn.net/xsdxs/article/details/53152599
压缩对比过程:
# 创建表,存储数据格式为 TEXTFILE
create table log_text
( track_time string, url string, session_id string, referer string, ip string, end_user_id string, city_id string )
row format delimited fields terminated by '\t'
stored as textfile ;
# 向表中加载数据
hive (default)> load data local inpath '/opt/module/datas/log.data' into table log_text ;
# 查看表中数据大小
hive (default)> dfs -du -h /user/hive/warehouse/log_text;
--18M
# 创建表,存储数据格式为 ORC
create table log_orc
( track_time string, url string, session_id string, referer string, ip string,end_user_id string, city_id string )
row format delimited fields terminated by '\t'
stored as orc ;
# 向表中加载数据
hive (default)> insert into table log_orc select * from log_text ;
# 查看表中数据大小
hive (default)> dfs -du -h /user/hive/warehouse/log_orc/ ;
--2.8M
# 创建表,存储数据格式为 parquet
create table log_parquet
( track_time string, url string, session_id string, referer string, ip string, end_user_id string, city_id string )
row format delimited fields terminated by '\t'
stored as parquet ;
# 向表中加载数据
hive (default)> insert into table log_parquet select * from log_text ;
# 查看表中数据大小
hive (default)> dfs -du -h /user/hive/warehouse/log_parquet/ ;
--13.1M
存储文件的压缩比总结:
ORC > Parquet > textFile
注意,除了textFile外的其他三种是不能直接从本地文件导入到hdfs的,数据需要先导入到textFile格式的表中,然后再通过textFile的表用insert导入到他们自身对应格式的表中。
hive (default)> select count(*) from log_text;
hive (default)> select count(*) from log_orc;
hive (default)> select count(*) from log_parquet;
存储文件的查询速度总结:查询速度相近。
11.4 存储和压缩结合
11.4.1 修改Hadoop 集群具有 Snappy 压缩方式
# 查看Hadoop支持的压缩方式
[atguigu@hadoop104 hadoop-2.7.2]$ hadoop checknative
hadoop默认是不支持snappy压缩的,需要自行安装配置。
# 将编译好的支持 Snappy 压缩的 hadoop-2.7.2.tar.gz 包导入到 hadoop102 的 /opt/software 中
# 然后解压
[atguigu@hadoop102 software]$ tar -zxvf hadoop-2.7.2.tar.gz
# 进入到/opt/software/hadoop-2.7.2/lib/native 路径可以看到支持 Snappy 压缩的 动态链接库
[atguigu@hadoop102 native]$ pwd
/opt/software/hadoop-2.7.2/lib/native
[atguigu@hadoop102 native]$ ll
-rw-r--r--. 1 atguigu atguigu 472950 9 月 1 10:19 libsnappy.a
-rwxr-xr-x. 1 atguigu atguigu 955 9 月 1 10:19 libsnappy.la
lrwxrwxrwx. 1 atguigu atguigu 18 12 月 24 20:39 libsnappy.so -> libsnappy.so.1.3.0 lrwxrwxrwx. 1 atguigu atguigu 18 12 月 24 20:39 libsnappy.so.1 -> libsnappy.so.1.3.0
-rwxr-xr-x. 1 atguigu atguigu 228177 9 月 1 10:19 libsnappy.so.1.3.0
# 拷贝/opt/software/hadoop-2.7.2/lib/native 里面的 所有内容到 开发集群的 /opt/module/hadoop-2.7.2/lib/native 路径上(还是102)
[atguigu@hadoop102 native]$ cp ../native/* /opt/module/hadoop-2.7.2/lib/native/
# 分发集群
[atguigu@hadoop102 lib]$ xsync native/
# 再次查看就可以看到支持了。
[atguigu@hadoop102 hadoop-2.7.2]$ hadoop checknative
注意,最后要重新启动 hadoop 集群和 hive才会生效。
11.4.2 测试存储和压缩
官网:https://cwiki.apache.org/confluence/display/Hive/LanguageManual+ORC
ORC 存储方式的压缩:
这里我们用一个非压缩的Orc存储和一个采用Snappy压缩的Orc存储做对比:
# 创建一个非压缩的的 ORC 存储方式
create table log_orc_none
( track_time string, url string, session_id string, referer string, ip string, end_user_id string, city_id string )
row format delimited fields terminated by '\t'
stored as orc tblproperties ("orc.compress"="NONE");
hive (default)> insert into table log_orc_none select * from log_text ;
# 查看插入后数据
hive (default)> dfs -du -h /user/hive/warehouse/log_orc_none/ ;
--7.7M
# 创建一个 SNAPPY 压缩的 ORC 存储方式
create table log_orc_none
( track_time string, url string, session_id string, referer string, ip string, end_user_id string, city_id string )
row format delimited fields terminated by '\t'
stored as orc tblproperties ("orc.compress"="SNAPPY");
hive (default)> insert into table log_orc_snappy select * from log_text ;
# 查看插入后数据
hive (default)> dfs -du -h /user/hive/warehouse/log_orc_snappy/ ;
--3.8 M
# 上一节中默认创建的 ORC 存储方式,导入数据后的大小为2.8M,比用snappy的还要小
#这是因为orc默认使用的就是zlib压缩
总结:虽然orc默认的zlib压缩方式比snappy还小,但snappy的压缩和解压缩效率比较高,所以压缩方式一般选择snappy。在实际的项目开发当中,hive 表的数据存储格式一般选择:orc 或 parquet。压缩方式一
般选择 snappy,lzo。
十二、Hive企业级调优
12.1 Fetch抓取
Fetch抓取是指Hive中对某些查询可以不必使用mapreduce计算。
比如select * from
在这种情况下,Hive可以简单地读取表对应存储目录下的文件,然后输出查询结果到控制台。
在配置文件hive-default.xml.template文件中,默认设置hive.fetch.task.conversion=more,这样在全局查找,字段查找和limit查找等都不走mapreduce。
set hive.fetch.task.conversion=more;
#直接返回,不走mapreduce
select * from t1;
select sex from t1;
select * from t1 limit5;
12.2 本地模式
Hive默认情况下是启用hadoop的job模式,把任务提交到集群中运行,但我们可以通过设置本地模式让hive在单台机器上处理任务,对于小数据集,执行时间可以明显被缩短。
#开启本地模式,并执行查询语句、
set hive.exec.mode.local.auto=true;
#设置本地模式的最大输入数据量,当输入数据量小于这个值时就采用本地模式,默认为128M
set hive.exec.mode.local.auto.inputbytes.max=50000000;
#设置本地模式的最大输入文件个数,当输入文件个数小于这个数字时就启用本地模式,默认为4
set hive.exec.mode.local.auto.input.files.max=10;
12.3 表的优化
12.3.1 小表、大表 Join
可以将 key 相对分散,并且数据量小的表放在 join 的左边,这样可以有效减少内存溢出错误发生的几率;
再进一步,可以先让小表(1000条记录以下的)进入到内存中,后续大表加载时可以直接从内存中获取小表的数据,整个过程都在map端执行,不需要用到reduce,也就不需要用到mr中的shuffle,能提升查询效率。
map join就是让多个数据结果直接在map端完成聚合操作,也就是在map端实现reduce端的逻辑。
12.3.2 大表join大表
可能遇到查询慢的情况处理:
-
空key过滤
有时 join 超时是因为某些 key 对应的数据太多,而相同 key 对应的数据都会发送到相同 的 reducer 上,从而导致内存不够。此时我们应该仔细分析这些异常的 key,很多情况下, 这些 key 对应的数据是异常数据,我们需要在 SQL 语句中进行过滤。例如 key 对应的字段 为空,
# 在语句中排除空值
hive (default)> insert overwrite table jointable
select n.* from
(select * from nullidtable where id is not null
) n left join ori o on n.id = o.id;
-
空key转换
有时虽然某个 key 为空对应的数据很多,但是相应的数据不是异常数据,必须要包含在join 的结果中,此时我们可以表 a 中 key 为空的字段赋一个随机的值,使得数据随机均匀地分不到不同的 reducer 上。例如:
insert overwrite table jointable
select n.* from nullidtable n
full join ori o
on case when n.id is null then concat('hive', rand()) else n.id end = o.id;
这里是假设连接字段为空比较多,直接用拼接字符串去给他随机赋值,这样就能平均分配到不同reduce执行。
12.3.3 MapJoin
如果不指定map join或者不符合map join的条件,那么Hive解析器会将join操作转换成common join,即在reduce阶段完成join,容易发生数据倾斜,可以用map join把小表全部加载到内存,在map端进行join,避免reduce处理。
- 工作机制:简单来说就是先将小表存入内存中,然后加载到分布式缓存,后面大表会另外启动一个没有reduce的task,并在map阶段和刚刚存储在分布式缓存中的小表关联合并,然后输出结果文件。有多少个map task 就有多少个结果文件。
insert overwrite table jointable
select b.id, b.time, b.uid, b.keyword, b.url_rank, b.click_num, b.click_url
from smalltable s
join bigtable b
on s.id = b.id;
12.3.4 设置hive的group by参数
默认情况下,Map 阶段同一 Key 数据分发给一个 reduce,当一个 key 数据过大时就倾斜了。
并不是所有的聚合操作都需要在reducec端完成,很多聚合操作都可以现在map端先进行部分聚合,最后在reduce端完成最终的聚合操作。
案例:
# 是否在map段進行聚合,默认是true
set hive.map.aggr = true
# 在map端进行聚合操作的条目数目
set hive.groupby.mapaggr.checkinterval = 100000;
# 有数据倾斜的时候进行负载均衡,比如把相同特征的key分发到不同的reduce中(默认是false)
set hive.groupby.skewindata = true;
当选项设定为 true,生成的查询计划会有两个 MR Job。第一个 MR Job 中对应负载均衡的设置,Map 的输 出结果会随机分布到 Reduce 中,每个 Reduce 做部分聚合操作,并输出结果,这样处理的结果是相同的 Group By Key 有可能被分发到不同的 Reduce 中,从而达到负载均衡的目的;第 二个 MR Job 再根据预处理的数据结果按照 Group By Key 分布到 Reduce 中(这个过程可以 保证相同的 Group By Key 被分布到同一个 Reduce 中),最后完成最终的聚合操作。
12.3.5 Count(distinct)去重统计
数据量小的时候无所谓,数据量大的情况下,由于 COUNT DISTINCT 操作需要用一个Reduce Task 来完成,这一个 Reduce 需要处理的数据量太大,就会导致整个 Job 很难完成,一般 COUNT DISTINCT 使用先 GROUP BY 再 COUNT 的方式替换:
# 每个reduce任务处理的数据量默认是256M
# 这个设置其实可以不用改。
set hive.exec.reducers.bytes.per.reducer= 32123456;
select count(distinct id) from log_text;
# 改成:
select count(id) from (select id from log_text group by id);
# 虽然可能会多开一个job来运行group by,但换取的查询速度是值得的。
12.3.6 避免笛卡尔积
尽量避免笛卡尔积,产生笛卡尔积时,hive只能使用一个reduce来做相应处理,那么等待时间就变很长。
12.3.7 行列过滤
列处理:在 SELECT 中,只拿需要的列,如果有,尽量使用分区过滤,少用 SELECT *。
行处理:在分区剪裁中,当使用外关联时,如果将副表的过滤条件写在 Where 后面,那么就会先全表关联,之后再过滤,所以建议副表先过滤后关联。
比如:
select o.id from bigtable b join ori o on o.id = b.id where o.id <= 10;
--Time taken: 34.406 seconds, Fetched: 100 row(s)
select b.id from bigtable b join (select id from ori where id <= 10 ) o on b.id = o.id;
--Time taken: 30.058 seconds, Fetched: 100 row(s)
12.3.8 动态分区调整
Hive使用动态分区前,需要先进行配置。
# 开启动态分区,默认是开启的
set hive.exec.dynamic.partition=true
# 设置为非严格模式,动态分区的模式,默认 strict,表示必须指定至少一个分区为 静态分区,nonstrict 模式表示允许所有的分区字段都可以使用动态分区
set hive.exec.dynamic.partition.mode=nonstrict
# 在所有执行 MR 的节点上,最大一共可以创建多少个动态分区。
set hive.exec.max.dynamic.partitions=1000
# 在每个执行 MR 的节点上,最大可以创建多少个动态分区。(这里是每个MR,上面是一共能创建多少个)
# 该参数需要根据实际 的数据来设定。比如:源数据中包含了一年的数据,即 day 字段有 365 个值,那么该参数就 需要设置成大于 365,如果使用默认值 100,则会报错。
set hive.exec.max.dynamic.partitions.pernode=100
# 整个 MR Job 中,最大可以创建多少个 HDFS 文件。
set hive.exec.max.created.files=100000
# 当有空分区生成时,是否抛出异常。一般不需要设置。
set hive.error.on.empty.partition=false
12.3.9 使用分区或分桶
分区参见Hive 七-7.5 Hive的分区表。
分桶参见Hive 七-7.6 Hive的分桶表。
12.4 数据倾斜问题
hive针对数据倾斜,还有以下方法可以解决
12.4.1 合理设置Map数
- 通常情况下map数是根据输入文件总格数,文件大小和集群设置的文件块大小决定的,如果一个任务中有很多小文件,每个文件都当作一个块需要一个map任务来运行,那么此时不是越多map数越好,map任务的启动和初始化过程也会占用时间,这时就应该考虑减少map数
- 另外一种情况,假如某个map它处理的逻辑比较负责,比如某个文件有几千万的记录,那么只交给一个map处理的话明显耗费的时间很长,这时可以考虑增加map数
12.4.2 在map执行之前将小文件合并可以减少map数
-
在map执行前合并小文件,减少map数
- CombineHiveInputFormat具有对小文件进行合并的功能(系统默认的格式)
set hive.input.format = org.apache.hadoop.hive.ql.io.CombineHiveInputFormat;
12.4.3 对复杂文件可以增加map数
-
当输入的文件都很大,任务逻辑复杂,map执行非常缓慢的时候可以设当增加map数,来使得每个map处理的数据量减少,从而提高速度
-
增加map方法为:
- 根据computeSliteSize(Math.max(minSize,Math.min(maxSize,blocksizs))) = blocksize = 128M公式
- 调整maxSize最大值,让maxSize小于blocksize就可以增加map数
mapreduce.input.fileinputformat.split.minsize = 1;# 默认值为1 mapreduce.input.fileinputforma.split.maxsize = long.MAXValue;#默认值 #有这样一个公式:Math.max(minSize,Math.min(maxSize,blockSize)) #比如这样设置就可以达到增加map数的效果 #设置maxSize大小为10M,这样小于一个block128M的话,系统就会分配更多10M的map来处理复杂任务。 set mapreduce.input.fileinputformat.split.maxsize = 10485760;
12.4.4 合理设置Reduce数
-
1.调整reduce个数的方法一:
- 设置每个reduce处理的数据量,默认是256M
set hive.exec.reducers.bytes.per.reducer = 256000000;
- 设置每个任务最大的reduce数,默认是1009
set hive.exec.reducers.max = 1009;
这样reduce的个数就处于这样一个范围值:N = (总输入数据量 / 每个reduce处理的数据量,每个任务最大的reduce数)
-
2.调整reduce个数的方法二:
- 设置每个job中reduce的个数,默认是1
set mapreduce.job.reduces = 3;
注意,但是reduce也不是越多越好的,过多的启动和初始化 reduce 也会消耗时间和资源;另外,有多少个reduce,就会有多少个输出文件,如果生成了很多个小文件,那么如果这些小文件作为下一个任务的输入,则也会出现小文件过多的问题;
12.5 并行执行
和oracle一样也可以利用并行执行提高查询速度,不同的是hive是靠参数来控制的,一定要在系统资源空闲时使用。
#开启并行执行
set hive.exec.parallel = true;
#设置同一个sql允许的最大并行度,默认是8
set hive.exec.parallel.thread.number = 16;
12.6 严格模式
hive有可以设置严格模式,可以防止用户执行那些可能意想不到的,或者有不好影响的查询,比如等待了长时间发现是笛卡尔积,或者查询大表时忘记加分区条件。
set hive.mapred.mode = strict;
开启严格模式后,就可以禁止以下三种操作:
-
(1)对于分区表,在where条件中必须要有分区字段的过滤条件来限制范围;
-
(2)对于使用了order by的查询,要求使用limit语句
-
(3)笛卡尔积;
违反以上三种情况,系统都会直接报错,不允许执行。
12.7 JVM重用
JVM重用可以使得JVM实例在同一个job中重新使用多次,减少进程的启动和销毁时间。
JVM 重用是 Hadoop 调优参数的内容,其对 Hive 的性能具有非常大的影响,特别是对于很难避免小文件的场景或 task 特别多的场景,这类场景大多数执行时间都很短。
Hadoop 的默认配置通常是使用派生 JVM 来执行 map 和 Reduce 任务的。这时 JVM 的启动过程可能会造成相当大的开销,尤其是执行的 job 包含有成百上千 task 任务的情况。JVM重用可以使得 JVM 实例在同一个 job 中重新使用 N 次。N 的值可以在 Hadoop 的mapred-site.xml 文件中进行配置。通常在 10-20 之间,具体多少需要根据具体业务场景测试
得出。
#设置jvm重用个数
set mapred.job.reuse.jvm.num.tasks = 5;
这个功能的缺点是,开启 JVM 重用将一直占用使用到的 task 插槽,以便进行重用,直到任务完成后才能释放。如果某个“不平衡的”job 中有某几个 reduce task 执行的时间要比其 他 Reduce task 消耗的时间多的多的话,那么保留的插槽就会一直空闲着却无法被其他的 job使用,直到所有的 task 都结束了才会释放。
12.8 推测执行
Hadoop采用了推测执行机制,它根据一定的法则推测出”拖后腿“的任务,并为这样的任务启动一个备份任务,让备份任务和原始任务同时处理一份数据,并最后选择优先执行完成的任务计算结果作为最终结果。
#开启推测执行机制
set hive.mapred.reduce.tasks.speculative.exection = true;
注意,如果用户因为输入数据量很大而需要执行长时间的 map 或者 Reduce task 的话,那么启动推测执行造成的浪费是非常巨大大。
12.9 使用压缩
详见Hive-十一、Hive压缩和存储
- Hive表中间数据压缩
#设置为true为激活中间数据压缩功能,默认是false,没有开启
set hive.exec.compress.intermediate = true;
#设置中间数据的压缩算法
set mapred map.output.compression = codec = org.apache.hadoop.io.compress.SnappyCodec;
- Hive表最终输出结果压缩
set hive.exec.compress.output = true;
set mapred map.output.compression = codec = org.apache.hadoop.io.compress.SnappyCodec;
12.10 查看执行计划
1.基本语法
EXPLAIN [EXTENDED | DEPENDENCY | AUTHORIZATION] query
2.案例实操
(1)查看下面这条语句的执行计划
hive (default)> explain select * from emp;
hive (default)> explain select deptno, avg(sal) avg_sal from emp group by deptno;
(2)查看详细执行计划
hive (default)> explain extended select * from emp;
hive (default)> explain extended select deptno, avg(sal) avg_sal from emp group by
deptno;
Hive课后题
1. 将数据直接传到HDFS分区目录上,怎么让分区表和数据产生关联?
因为上传到hdfs后,hive没有对应元数据信息所以无法查询到对应数据。可以上传数据后给分区表添加该目录的分区
dfs -mkdir -p 分区目录
dfs -put 分区数据
hive>alter table 表明 add partition(分区);
2. 桶表是否可以直接通过load将数据导入?
不可以,因为load数据的话hdfs下只会有一个文件无法完成分桶的效果,需要通过中间表导入数据
3. hive的分区可以提高效率,那么分区是否越多越好?为什么?
不是越多越好
- hive底层是存储在hdfs上的,hdfs是适合存储大文件而不适合小文件,如果有越多的分区,那么会增加namenode的负担。
- hive会转化成mr程序,mr会转化为多个task任务,多个个小文件的话,每个文件一个task,每个task运行一个JVM实例,JVM的开启和销毁都会降低系统性能。
所以分区数要合理设计,一般在3个以内。
4. 什么情况下Hive可以避免进行mapreduce?
- 如果是进行简单的查询,直接select,不带count,sum这些聚合函数的,都不会走mapreduce,而是直接读取hdfs目录中的文件。
- 另外如果查询语句中的过滤条件只是分区字段的情况下,也不会走mapreduce
select * from order_partition where month = '2019-03';
- 还有就是可以手动设置,让hive使用本地模式,当然这种有限制,需要查询的文件不超过256M或者文件数量不超过4个,否则系统还是会自动走mapreduce
set hive.exec.mode.local.auto = true;
5. order by ,sort by , distribute by , cluster by 的区别?
Order by会对所给的全部数据进行全局排序,只启动一个reduce来处理。
Sort by是局部排序,它可以根据数据量的大小启动一到多个reducer来工作,并且在每个reduce中单独排序。
Distribute by 类似于mr中的partition,采用hash算法,在map端将查询结果中hash值相同的结果分发到对应的reduce中,结合sort by使用
Cluster by 可以看作是distribute by 和sort by的结合,当两者后面所跟的字段列名相同时,效果就等同于使用cluster by,但是cluster by最终的结果只能是降序,无法指定升序和降序。
6. 如何将数据以动态分区的方式插入分区表中?
-
1.首先创建对应分区表和一张普通表
-
2.然后将数据加载到普通表
load data local inpath '/opt/bigdata/order_partition' into table tt_order;
- 3.最后利用普通表来将数据加载到动态分区表中
先设置使用动态分区的参数和使用非严格模式
set hive.exec.dynamic.partition=true;
set hive.exec.dynamic.partition.mode=nonstrict;
然后通过普通表导入分区表
insert into table order_partition partition(year,month) select order_number,order_price,substring(order_time,0,4) as year,substring(order_time,6,12) as month from tt_order;
注意导入的字段顺序,分区键一定要放在最后,否则会报错。
7. 数据倾斜现象和解决办法?(重要)
-
1.什么是数据倾斜?
大量相同特征的key出现在同一个reduce任务中,或者某个key对应的数据量远超过其它key的数据量,这种导致数据分布不均匀的现象就叫做数据倾斜。
-
2.数据倾斜的现象
在执行任务的时候,任务进度长时间卡在99%左右,查看任务监控页面或者详细日志信息,发现只有少量,一个或者几个reduce子任务没有跑完,主要因为这几个reduce任务处理的数据量和其它reduce任务差异过大。这种单一reduce任务的记录数与平均记录数差异过大,,就会极大拖长计算时间。
现实工作中可能会遇到这样的情况比较多:比如大表join小表,其中小表有特别的key值比较集中,这样分发到某一个reduce上的数据就会高于平均值;或者是大表join大表中,作为连接判断的字段0值或者空值较多的,这些0值和空值后续都会由一个reduce处理,导致这个reduce处理量过多;再有的情况就是group by、count( distinct )某个字段值数据多而导致reduce处理耗时的情况。
-
3.数据倾斜的原因
- key分布不均匀,比如空值,0值
- 业务数据本身的特性。
- 建表时考虑不周,导致后期join操作时数据倾斜
- 某些sql语句本身就有数据倾斜。比如用count(distinct),它会单独用一个reduce来计算统计,如果数据量很大,就会导致整个job很难完成。这种情况可以先用group by分出需要统计的字段,再进行sum或者count
-
4.数据倾斜的解决方案,有三个层面可以思考处理:
-
第一,SQL语句调优
-
查询语句加上具体需要的列和分区键,有些复杂表的字段会存储json格式的文本,这些字段不一定是需要查询的就可以过滤掉,减轻reduce计算负担
-
大表join小表时用map jion,让小表先进内存,然后大表与小表在map端完成join操作,避免reduce端处理。
-
大表join大表中,可以把空值的key变成一个字符串然后加上rand()随机数,后续mr的分区操作会把倾斜的数据重新分发到不同的reduce上,从而避免数据倾斜。或者在join 的on条件中先让key为空的值 不参与关联,等key不为空的数据相互合并连接后再union all加回key为空的数据。
select * from a left outer join b on case where id is null then concat('任意字符串',rand()) else id end = b.id; select * from log a join users b on a.id is not null and a.id = b.id union all selct * from log a where a.id is null;
-
查询语句中count(distinct) 改成group by + sum(),比如
select count(distinct id) from test; ==> select sum(id) from (select id from test group by id); 这种可能会多开一个reduce来完成group by的操作,但会明显提高查询速度。
-
针对不同数据类型产生的数据倾斜,存在这样的情况,A表中的id字段的数据类型是int,但join的B表中id字段存在脏数据,有一些是int类型但也有string类型的,那么再join操作时,默认的hash操作就会对int类型的key进行分配,而对于string类型的key会被统一分配到一个reduce中,这种情况就需要先进行类型转换,如 a join b on a.id = cast(b.id as int);
-
还有一些时候可以把数据倾斜的数据单独拿出来处理,然后再union all回去。
-
-
第二,通过设置hive参数配置解决,这种主要是优化计算速度,避免数据倾斜发生
-
开启map端聚合
并不是所有的聚合操作都需要在reducec端完成,很多聚合操作都可以现在map端先进行部分聚合,最后在reduce端得出最终结果(类似于mr过程中的combiner,预先合并压缩数据,再提供给reduce统计计算)
再hive开启map端聚合后,一旦发现数据倾斜,系统就能自动负载均衡,把相同特征的key分发到不同的reduce中,主要通过hive.groupby.skewindata参数完成。
开启map端聚合的设置 是否在map段進行聚合,默认是true set hive.map.aggr = true 在map端进行聚合操作的条目数目 set hive.groupby.mapaggr.checkinterval = 100000; 有数据倾斜的时候进行负载均衡,比如把相同特征的key分发到不同的reduce中(默认是false) set hive.groupby.skewindata = true;
-
设置并行执行
和oracle一样也可以利用并行执行提高查询速度,不同的是hive是靠参数来空值的
开启并行执行 set hive.exec.parallel = true; 设置同一个sql允许的最大并行度,默认是8 set hive.exec.parallel.thread.number = 16;
-
设置压缩
压缩可以在map端要进行shuffle时压缩和在完成reduce输出时压缩
- Hive表中间数据压缩
设置为true为激活中间数据压缩功能,默认是false,没有开启 set hive.exec.compress.intermediate = true; 设置中间数据的压缩算法 set mapred map.output.compression = codec = org.apache.hadoop.io.compress.SnappyCodec;
- Hive表最终输出结果压缩
set hive.exec.compress.output = true; set mapred map.output.compression = codec = org.apache.hadoop.io.compress.SnappyCodec;
-
推测执行
说简单点就是Hadoop用了一个备胎来同时执行,跟原来的任务相比较,谁先执行完成就用谁的计算结果作为最终的计算结果。具体定义如下:
Hadoop采用了推测执行机制,它根据一定的法则推测出”拖后腿“的任务,并为这样的任务启动一个备份任务,让备份任务和原始任务同时处理一份数据,并最后选择优先执行完成的任务计算结果作为最终结果。
#开启推测执行机制 set hive.mapred.reduce.tasks.speculative.exection = true;
-
JVM重用
JVM重用可以使得JVM实例在同一个job中重新使用多次,减少进程的启动和销毁时间
#设置jvm重用个数 set mapred.job.reuse.jvm.num.tasks = 5;
-
合理设置map数和reduce数
- 在map执行之前将小文件合并可以减少map数
系统默认的格式,可以不用设置。 set hive.input.format = org.apache.hadoop.hive.ql.io.CombineHiveInputFormat;
- 对复杂文件可以增加map数
增加map方法有一个公式: compute(SliteSize(Math.max(minSize,Math.min(maxSize,blocksizs))))公式 - 调整maxSize最大值,让maxSize小于blocksize就可以增加map数 - minSize默认等于1,maxSize默认等于blockSize大小。 比如这样设置就可以达到增加map数的效果 设置每个map处理的文件maxSize大小为10M,这样小于一个block128M的话,系统就会分配更多10M的map来处理复杂任务。 set mapreduce.input.fileinputformat.split.maxsize = 10485760;
- 合理设置Reduce数,比如设置每个job中reduce的个数为3个
set mapreduce.job.reduces = 3;
-
-
第三,修改MR程序去避免数据倾斜
-
可以在MR程序的reduce方法中追踪每个键的最大值,并且设置阈值,当超过该阈值时就可以认为发生了数据倾斜,然后输出到日志文件进行分析。
-
第二种是在编写MR程序时,从业务层面去考虑自定义的分区键是否合理。就跟ADS库建表时可以默认指定哪个字段作为分区键。
-
MR程序中改用TotalOrderPartitioner替换HashPartitioner,它可以通过对原始数据进行抽样得到的结果集来预设分区边界值,也就是能找出导致数据倾斜的key值,再分散处理。
-
MR程序中使用Combiner。
-
-
修改记录
时间 | 内容 |
---|---|
2020年04月10日 | 第一次发布 |
2020年9月13日 | 结合新课程,重新整理知识点框架 |
2020年9月14日 | 上传知识点结构图 |
- 学习参考:
《开课吧-大数据开发高级工程师一期》课程
《尚硅谷大数据项目数据仓库,电商数仓V1.2新版》课程
本文为学习课程做的笔记,如有侵权,请联系作者删除。