一、hive底层逻辑
GROUP BY 原理
select name,count(num) from tb group by name
map 阶段,将 group by 后的字段组合作为 key,如果 group by 单字段那么 key 就一个。
将 group by 之后要进行的聚合操作字段作为值,如要进行 count,则 value 是 1;如要 sum 另一个字段,则 value 就是该字段。
shuffle 阶段,按照 key 的不同分发到不同的 reducer。注意此时可能因为 key 分布不均匀而出现数据倾斜的问题。
reduce 阶段,将相同 key 的值累加或作其他需要的聚合操作,得到结果。
JOIN原理
count(distinct) 原理
select num,count(distinct name) as count_name from tb
Hadoop作业称为Job,Job分为Map、Shuffle和Reduce阶段,MAP和Reduce的Task都基于JVM进程运行的。
MAP阶段:从HDFS读取数据,split文件产生task,通过对应数量的Map处理,map输出的每一个键值对通过key的hash值计算一个partition,数据通过环形缓冲区,sort、spill、merge生成data和index文件;
Reduce阶段:reduce通过Shuffle Copy属于自己的那部分数据,然后sort、spill、merge形成一个大文件,调用reduce函数进行处理.
二、数据倾斜场景以及解决方法
常见的数据倾斜场景有group by , join , count(distnct)
1 group by
原因:默认情况下,map阶段相同的 key 会分发到同一个reduce 端聚合,热点 key 导致 group by 聚合倾斜
解决办法:
---开启map 端聚合: set hive.map.merge.aggr= true; 一般是默认开启 ,减少数据传输及在reduce阶段的处理时间.
set hive.groupby.mapaggr.checkinterval=10000; 设置在map端聚合操作的条数.当大于该值时才会在map端聚合
---开启负载均衡: set hive.groupby.skewindata=true; 该设置会在数据出现倾斜时,将一个group by 的执行计划拆分成2个阶段MR,第一个阶段将key 随机分发到不同的reduce进行聚合,如何第二阶段MR会基于前一阶段预处理的结果,将相同的key分发到同一个reduce算子,执行聚合操作
---小文件过多时,合并小文件: set hive.merge.mapredfiles=true;
2 join
a .小表 join 大表
原因:主要是热点key 导致,在shuffle 过程中,相同的key 会基于hash 分发到同一个 reduce 算子上,导致join 倾斜
解决办法:
---开启map 端 join: set hive.auto.convert.join=true; 一般默认是打开的
开启mapjoin后,join时会在map端将小表缓存到内存中(缓存为hash table) ,在map 端执行 join.
小表默认是1000行或者25MB大小,可以设置参数提高小表的阈值:
set hive.mapjoin.smalltable.filesize=25000000;
b 大表 join 大表
原因:跟大表join 下表倾斜原因差不多,存在热点key ,或者大量无用的空key,导致倾斜
解决办法:
---运行时优化: set hive.optimize.skewjoin=true; 开启倾斜关联开启对倾斜数据join的优化
设置join 倾斜阈值: set hive.skewjoin.key=100000; 当join对应的key记录的条数超过10000,就可以人为这个key 发生了数据倾斜,会对其进行分拆优化
---编译时优化: set hive.optimize.skewjoin.compiletime=true; 可以在编译SQL时将计划优化完成,但是需要在建表时指定数据倾斜元数据信息
---union 优化: set hive.optimize.union.remove=true;
一般情况下,这三种优化都要同时开启
除了以上参数外,还可以通过 子查询剔除空key, 空key 转为随机字符串,大表拆分成小表 ,分别join 再 union 等方式进行优化
3 count(distinct)
原因:所有key 会分发到同一个reduce 进行去重统计个数,数据量较大时会导致运行时间过长
解决办法:改成group by
SELECT COUNT(DISTINCT id)
FROM table;
改成
SELECT COUNT(t1.id)
FROM ( SELECT id
FROM tabe
GROUP BY id
) t1
;
补充:4 map和reduce
原因1:当出现小文件时,需要合并小文件
解决办法:
reduce端输出合并: set hive.merge.mapredfiles=true;
原因2:输入数据存在大块和小块的严重问题
解决办法:
任务输入前做文件合并,将小文件合并成大文件: set hive.merge.mapredfiles=true;
原因3:单个文件大小稍大于配置的block 块的大小
解决办法:
此时需要增加map task的个数: set mapred.map.tasks=25;
原因4:文件大小适中,但是map端计算量非常大
解决办法:
增加map和reduce个数:set mapre.map.tasks=250;
set mapre.reduce.tasks=250;
三、hive调优
1.建表优化
a 分区优化
建分区表,使用这个字段做分区字段,在查询的时候,使用分区字段来过滤就可以避免全表扫描,只需要扫描这张表的一个分区的数据即可
1.分区表定义
分区表实际上就是对应一个hdfs文件系统上的独立的文件夹,该文件夹下是该分区所有的数据文件.hive中的分区就是分目录,把一个大的数据集根据业务需要分割成小的数据集,在查询时通过where子句中的表达式选择查询所需要的指定的分区,这样查询效率会提高很多.(谓词下推)
表分区就是会在表目录下面针对不同的分区创建一个子目录,如果有二级分区,那么会在一级子目录下面计息创建子目录.hive中的分区字段是表外字段,mysql中的分区字段是表内字段
2.创建分区表语法
create table dept_partition(deptno int,dname string,loc string)
partitioned by (month string)---按月分组
row format delimited fields
分区字段是 month,分区的数据类型是string,分区字段不能是表里的字段
3.创建二级分区语法
create table dept_partition(deptno int,dname string,loc string)
partitioned by (month string,year string)---按月和年分组
row format delimited fields
create table if not exists employee_partitioned (
`name` string,
`employee_id` int,
`number` string)
partitioned by (
`year` int,
`month` int)
row format delimited fields terminated by '|'
stored as textfile;
year 为一级分区,month为二级分区
4.静态分区与动态分区
4.1 静态分区(默认)
4.1.1静态分区创建表
create table ods_users(
userid string,
username string,
birthday string,
sex string
)
row format delimited fields terminated by ','
location '/pw';
4.1.2静态分区操作:增加或删除分区语法
-- 创建分区
//单个分区创建
alter table employee_partitioned add partition(year='2019',month='6') ;
//多个分区创建
alter table employee_partitioned add partition(year='2020',month='6') partition(year='2020',month='7');
-- 删除分区
//单个分区删除
alter table employee_partitioned drop partition (year='2019',month='6');
//多个分区删除
alter table employee_partitioned partition (year='2020','month=6'), partition (year='2020',month='7');
4.1.3静态分区加载插入数据
load data local inpath '/文件路径' into table '表名' partition'(分区信息)'
hive> load data local inpath '/usr/local/wyh/test_data/user_partition1.txt'
into table part-demo1 partition(testdate='2022-04-28');
-- 静态分区加载二级分区
load data local inpath '/文件路径' into table '表名' partition'(分区信息1,分区信息2)'
hive> load data local inpath '/usr/local/wyh/test_data/user_partition2.txt'
into table part_demo2 partition(year='2022',month='03');
静态分区加载插入数据
insert into 将数据添加到表中
insert overwrite 会覆盖表中已有的数据
insert into userinfos partition(sex='male')
select userid,username,birthday from ods_users where sex='male';
insert into userinfos partition(sex='female')
select userid,username,birthday from ods_users where sex='female';
//将本地文件传入hdfs中
Hadoop dfs -put /data/employee_hr.txt /tmp/employee
//将文件映射为相应的表
create table if not exists employee_hr(
name string,
employee_id int,
number string,
start_date date)
row format delimited fields terminated by '|'
location '/data/employee_hr.txt';
//根据查询所得的结果传入相应的文件夹中
insert overwrite table employee_partitioned partition(year=2020,month=8)
select name,employee_id,number from employee_hr
where year(start_date)=2020 and month(start_date)=8;
4.2动态分区
4.2.1开启动态分区
动态分区默认是没有开启。开启后默认是以严格模式执行的,在这种模式下需要至少一个分区字段是静态的
关闭严格分区模式
动态分区模式时是严格模式,也就是至少有一个静态分区。
set hive.exec.dynamic.partition.mode=nostrict //分区模式,默认nostrict
set hive.exec.dynamic.partition=true //开启动态分区,默认true
set hive.exec.max.dynamic.partitions=1000 //最大动态分区数,默认1000set hive.exec.max.dynamic.partitions.pernode =100 :单个节点上的mapper/reducer允许创建的最大分区
strict:最少需要有一个是静态分区
nostrict:可以全部是动态分区
4.2.2动态分区的操作
(1)开启动态分区功能(默认true,开启)
set hive.exec.dynamic.partition=true
(2)设置为非严格模式(动态分区的模式,默认strict,表示必须指定至少一个分区为静态分区,nonstrict模式表示允许所有的分区字段都可以使用动态分区。)
set hive.exec.dynamic.partition.mode=nonstrict
(3)在所有执行MR的节点上,最大一共可以创建多少个动态分区。默认1000
set hive.exec.max.dynamic.partitions=1000
(4)在每个执行MR的节点上,最大可以创建多少个动态分区。该参数需要根据实际的数据来设定。比如:源数据中包含了一年的数据,即day字段有365个值,那么该参数就需要设置成大于365,如果使用默认值100,则会报错。
set hive.exec.max.dynamic.partitions.pernode=100
(5)整个MR Job中,最大可以创建多少个HDFS文件。默认100000
set hive.exec.max.created.files=100000
(6)当有空分区生成时,是否抛出异常。一般不需要设置。默认false
set hive.error.on.empty.partition=false
4.2.2创建动态分区表
create table if not exists dyp1
(uid int,
commentid bigint,
recommentid bigint)
partitioned by(year int,month int,day int)
row format delimited fields terminated by '\t';
create table myusers(
userid string,
username string,
birthday string
)
partitioned by (sex string)
row format delimited fields terminated by ','
stored as textfile;
--创建临时表
create table if not exists tmp
(uid int,
commentid bigint,
recommentid bigint,
year int,
month int,
day int)
row format delimited fields terminated by '\t';
4.2.3动态分区加载数据
load data local inpath '/root/Desktop/comm' into table tmp;
4.2.4插入数据(不指定分区值,该字段相同值就会分为一个分区)
insert into myusers partition(sex) select * from ods_users;
--严格模式动态分区插入数据
insert into table dyp1 partition(year=2016,month,day)
select uid,commentid,recommentid,month,day from tmp;
--为非严格模式动态分区插入数据
insert into table dyp2 partition(year,month,day)
select uid,commentid,recommentid,year,month,day from tmp;
5.静态分区和动态分区的区别及应用场景
5.1静态分区和动态分区的区别(静态分区的列是在编译时期,通过用户传递来决定的;动态分区只有在SQL执行时才能决定)
静态分区 | 动态分区 |
①静态分区是在编译期间指定的指定分区名 | ①根据分区字段的实际值,动态进行分区 |
②支持load和insert两种插入方式 | ②是在sql执行的时候进行分区 |
load方式 会将分区字段的值全部修改为指定的内容 一般是确定该分区内容是一致的时候才会使用 |
③需要先将动态分区设置打开 |
insert方式 必须先将数据放在一个没有设置分区的普通表中 该方式可以在一个分区内存储一个范围的内容 从普通表中选出的字段不能包含分区字段 |
④只能用insert方式 |
③适用于分区数少,分区名可以明确的数据 | ⑤通过普通表选出的字段包含分区字段,分区字段放置在最后,多个分区字段按照分区顺序放置 |
5.2静态分区与动态分区的应用场景
静态分区:适合已经确认分区的文件,分区相对较少的,适合增量导入的应用场景
动态分区:适合根据时间线做分区,分区比较多的,适合全量导入的场景
6.查看分区
show table dept_partition;
7.显示分区
show partitions iteblog;
7.删除分区
alter table dept_partition drop partition(month='2017-07') ,
partition(month='2017-08') ,
partition(month='2017-09');----注意逗号
8.增加分区
alter table dept_partition
add partition(month='2017-07')
partition(month='2017-08')
partition(month='2017-09');---直接空格,没有逗号
9.修改分区
ALTER TABLE iteblog PARTITION (dt='2008-08-08')
SET LOCATION "new location";
ALTER TABLE iteblog PARTITION (dt='2008-08-08')
RENAME TO PARTITION (dt='20080808');
10.修复分区
修复分区就是重新同步hdfs上的分区信息
msck repair table userinfos;
11.分区表的注意事项
--让分区表和数据产出关联的三种方式
先创建hdfs目录,再上传文件,然后刷新分区表的元数据(MSCK REPAIR TABLE 表名)
先创建hdfs目录,再上传文件,然后创建分区(alter table 表名 add partition。。。)
先创建hdfs目录,然后通过load语句加载数据文件。
--分区注意细节
(1)、尽量不要用动态分区,因为动态分区的时候,将会为每一个分区分配reducer数量,当分区数量多的时候,reducer数量将会增加,对服务器是一种灾难。
(2)、动态分区和静态分区的区别,静态分区不管有没有数据都将会创建该分区,动态分区是有结果集将创建,否则不创建。
(3)、hive动态分区的严格模式和hive提供的hive.mapred.mode的严格模式。
b 分桶优化
#创建分桶表
create table stu_buck(id int, name string)
clustered by(id)
into 4 buckets
row format delimited fields terminated by '\t';#创建中间表create table stu(id int, name string)