目录
[2.1 分区表的数据加载](#2.1 分区表的数据加载)
[2.1.1 静态分区](#2.1.1 静态分区)
[2.1.2 动态分区](#2.1.2 动态分区)
[2.2 分区表的使用及注意事项](#2.2 分区表的使用及注意事项)
[2.3 多重分区](#2.3 多重分区)
[3.1 分桶表的数据加载](#3.1 分桶表的数据加载)
[3.2 分桶表的使用及注意事项](#3.2 分桶表的使用及注意事项)
1.为什么需要分区和分桶
在 Hive 中,分区(Partitioning) 和 **分桶(Bucketing)**是两种核心的数据组织优化技术,用于提升查询性能、降低 I/O 开销、优化 Join 和采样效率。虽然目标相似,但二者原理和适用场景不同。分区针对的是数据的存储路径;分桶针对的是数据文件。

用一个例子来理解分区和分桶:想象一下,你要在一座巨大的图书馆里找一本关于"量子物理"的书。如果没有分类和索引,你需要遍历每一个书架(全表扫描),这无疑是噩梦。
-
分区 就像是图书馆先按领域(如:科学、文学、历史)划分了不同的阅览室。你想找量子物理的书,直接去"科学"阅览室即可,完全不需要去"文学"和"历史"阅览室浪费时间。
-
分桶 就像是你在"科学"阅览室里,书架上又按照作者姓氏的首字母进行了排序和分组。当你指定要找"Stephen Hawking"的书时,你可以直接走向"H"开头的书架,极大地缩小了搜索范围。
在 Hive 中,通过分区&分桶,跳过不需要的数据,大幅减少需要扫描的数据量,从而降低I/O开销和计算资源消耗,最终实现查询速度的飞跃。
2.分区(Partitioning)
当Hive表对应的数据量大、文件多时,为了避免查询时全表扫描数据,Hive支持根据用户指定的字段进行分区,分区的字段可以是日期、地域、种类等具有标识意义的字段。比如把一整年的数据根据月份划分12个月(12个分区),后续就可以查询指定月份分区的数据,尽可能避免了全表扫描查询。
分区的概念提供了一种将Hive表数据分离为多个文件/目录的方法。不同分区对应着不同的文件夹,同一分区的数据存储在同一个文件夹下。只需要根据分区值找到对应的文件夹,扫描本分区下的文件即可,避免全表数据扫描。
分区表建表语法:
sql
CREATE TABLE table_name (column1 data_type, column2 data_type) PARTITIONED BY (partition1 data_type, partition2 data_type, ...);
2.1 分区表的数据加载
分区表不能通过hadoop fs -put方式上传,因为这种方式没有办法实现分区效果,如果想实现数据分区,只能通过静态分区或动态分区的方式进行导入!
2.1.1 静态分区
静态分区指的是分区的字段值是由用户在加载数据的时候手动指定的,在已知分区值的情况下使用。语法如下:其中Local表示数据是位于本地文件系统还是HDFS文件系统。
sql
load data [local] inpath '数据文件路径' into table tablename partition(分区字段='分区值'...);
sql
# 创建一张分区表t_all_hero_part,以role角色作为分区字段
create table t_all_hero_part(
id int,
name string,
hp_max int,
mp_max int,
attack_max int,
defense_max int,
attack_range string,
role_main string,
role_assist string
) partitioned by (role string)
row format delimited
fields terminated by "\t";
sql
# 数据加载
load data local inpath '/root/hero/archer.txt' into table t_all_hero_part partition(role='sheshou');
load data local inpath '/root/hero/assassin.txt' into table t_all_hero_part partition(role='cike');
load data local inpath '/root/hero/mage.txt' into table t_all_hero_part partition(role='fashi');
load data local inpath '/root/hero/support.txt' into table t_all_hero_part partition(role='fuzhu');
load data local inpath '/root/hero/tank.txt' into table t_all_hero_part partition(role='tanke');
load data local inpath '/root/hero/warrior.txt' into table t_all_hero_part partition(role='zhanshi');
2.1.2 动态分区
往hive分区表中插入加载数据时,如果需要创建的分区很多,则需要复制粘贴修改很多sql去执行,效率低。因为hive是批处理系统,所以hive提供了一个动态分区功能,可以基于查询参数的位置去推断分区的名称,从而建立分区。动态分区指的是分区的字段值是基于查询结果自动推断出来的。核心语法就是insert+select。启用hive动态分区,需要在hive会话中设置两个参数:
sql
set hive.exec.dynamic.partition=true;
set hive.exec.dynamic.partition.mode=nonstrict;
第一个参数表示开启动态分区功能,第二个参数指定动态分区的模式,分为nonstick非严格模式和strict严格模式。如果某个分区表数据导入采用动态分区技术,必须要过两个步骤才能实现:
-
① 创建一个非分区数据表,把原始数据都导入这个表中。
-
② 通过insert + select语句实现把非分区表中的数据导入到分区表中。
sql
# 创建一张非分区表t_all_hero
create table t_all_hero(
id int,
name string,
hp_max int,
mp_max int,
attack_max int,
defense_max int,
attack_range string,
role_main string,
role_assist string
)
row format delimited
fields terminated by "\t";
#中间将数据导入非分区表t_all_hero
# 创建一张新的分区表t_all_hero_part_dynamic
create table t_all_hero_part_dynamic(
id int,
name string,
hp_max int,
mp_max int,
attack_max int,
defense_max int,
attack_range string,
role_main string,
role_assist string
) partitioned by (role string)
row format delimited
fields terminated by "\t";
# 执行动态分区插入,把非分区表t_all_hero中的数据导入到分区表t_all_hero_part_dynamic中
insert into table t_all_hero_part_dynamic partition(role) select tmp.*,tmp.role_main from t_all_hero tmp;
2.2 分区表的使用及注意事项
分区表的使用重点及注意事项在于:
-
1)建表时根据业务场景设置合适的分区字段。一般选择取值范围相对稳定、且常作为
WHERE过滤条件的字段,比如日期、地域、类别等; -
2)查询的时候尽量先使用where进行分区过滤,查询指定分区的数据,避免全表扫描。
-
3)分区字段不能是表字段,它是元数据,其数据并不存储在底层的文件中;
-
4)分区字段值的确定来自于用户价值数据手动指定(静态分区)或者根据查询结果位置自动推断(动态分区)
-
5)避免过度分区,分区数过多(如按用户ID分区)会导致小文件爆炸、NameNode 压力大。
查询英雄主要定位是射手并且最大生命大于6000的个数。使用分区表查询和使用非分区表进行查询,SQL如下:
sql
--非分区表 全表扫描过滤查询
select count(*) from t_all_hero where role_main="archer" and hp_max > 6000;
--分区表 同样的SQL,hive会先基于分区过滤 再查询
select count(*) from t_all_hero_part where role="archer" and hp_max > 6000;
2.3 多重分区
Hive支持多重分区,也就是在分区的基础上继续分区,划分更加细粒度。Hive支持多个分区字段:PARTITIONED BY (partition1 data_type, partition2 data_type,....)。多重分区下,分区之间是一种递进关系,可以理解为在前一个分区的基础上继续分区。从HDFS的角度来看就是文件夹下继续划分子文件夹。比如:把全国人口数据首先根据省进行分区,然后根据市进行划分,如果你需要甚至可以继续根据区县再划分,此时就是3分区表。
sql
--单分区表,按省份分区
create table t_user_province (id int, name string,age int) partitioned by (province string);
--双分区表,按省份和市分区
create table t_user_province_city (id int, name string,age int) partitioned by (province string, city string);
--三分区表,按省份、市、县分区
create table t_user_province_city_county (id int, name string,age int) partitioned by (province string, city string, county string);
多分区表的数据插入和查询使用
sql
load data local inpath '文件路径' into table t_user_province partition(province='shanghai');
load data local inpath '文件路径' into table t_user_province_city_county partition(province='zhejiang',city='hangzhou',county='xiaoshan');
select * from t_user_province_city_county where province='zhejiang' and city='hangzhou' and country='xiaoshan';
3.分桶(Bucketing)
分桶表也叫做桶表,源自建表语法中bucket单词。是一种用于优化查询而设计的表类型。该功能可以让数据分解为若干个部分易于管理。在分桶时,需要指定根据哪个字段将数据分为几桶。默认规则是:Bucket number = hash_function(bucketing_column) mod num_buckets,即对某一列的值进行哈希取模,将数据均匀分布到固定数量的文件(桶)中。
分桶表建表语法:
sql
CREATE TABLE table_name
[(col_name data_type, ...)]
CLUSTERED BY (col_name) SORTED BY (字段 asc|desc) INTO N BUCKETS;
其中CLUSTERED BY (col_name)表示根据哪个字段进行分桶,INTO N BUCKETS表示分为几桶(也就是几个部分)。需要注意的是,分桶的字段必须是表中已经存在的字段。
3.1 分桶表的数据加载
现有美国2021-1-28号,各个县county的新冠疫情累计案例信息,包括确诊病例和死亡病例,数据格式如下所示:
sql
2021-01-28,Juneau City and Borough,Alaska,02110,1108,3
2021-01-28,Kenai Peninsula Borough,Alaska,02122,3866,18
2021-01-28,Ketchikan Gateway Borough,Alaska,02130,272,1
2021-01-28,Kodiak Island Borough,Alaska,02150,1021,5
字段含义如下:count_date(统计日期),county(县),state(州),fips(县编码code),cases(累计确诊病例),deaths(累计死亡病例)。根据state州把数据分为5桶,建表语句如下:
sql
CREATE TABLE t_usa_covid19_bucket(
count_date string,
county string,
state string,
fips int,
cases int,
deaths int)
CLUSTERED BY(state) INTO 5 BUCKETS;
分桶表数据的加载只能通过insert + select方式实现,不能通过load data方式加载。
sql
#--step1:开启分桶的功能 从Hive2.0开始不再需要设置
set hive.enforce.bucketing=true;
#--step2:把源数据加载到普通hive表中
CREATE TABLE t_usa_covid19(
count_date string,
county string,
state string,
fips int,
cases int,
deaths int)
row format delimited fields terminated by ",";
#--将源数据上传到HDFS,t_usa_covid19表对应的路径下
hadoop fs -put us-covid19-counties.dat /user/hive/warehouse/itcast.db/t_usa_covid19
#--step3:使用insert+select语法将数据加载到分桶表中
insert into t_usa_covid19_bucket select * from t_usa_covid19 CLUSTER BY(state);
3.2 分桶表的使用及注意事项
1)基于分桶字段查询时,减少全表扫描。
2)高效的 Map-Side Join:如果两个表都按照相同的连接键(如 user_id)进行了分桶,且桶的数量相同或成倍数,Hive 可以对对应的桶直接进行连接操作,避免了最耗时的 Shuffle 阶段。
3)高效的采样(Sampling):对分桶表进行随机采样非常快且科学。
sql
#-- 直接抽取第1个桶的数据,速度快且均匀
SELECT * FROM user_behavior_bucketed TABLESAMPLE(BUCKET 1 OUT OF 5 ON user_id);
4)加速分组(group by)和去重(distinct):如果 GROUP BY 或 DISTINCT 的字段正好是分桶字段,因为相同值的数据已经在同一个桶内了,可以减少 Reduce 阶段的数据传输和计算量。
5)桶的数量:通常选择 2 的 N 次方,并且要大于集群的可用 Core 数量。
6)数据倾斜:分桶列的选择要尽量避免数据倾斜,确保数据能均匀分布到各个桶中。state 这种中高基数列是很好的选择,而 gender这种低基数列则不适合。
4.分区&分桶联合
在实际生产环境中,分区和分桶通常结合使用,注意只能先分区后分桶,实现多维度的数据优化。
sql
CREATE TABLE user_behavior_optimized (
user_id BIGINT,
item_id BIGINT,
behavior_type STRING,
...其他字段
)
PARTITIONED BY (dt STRING) -- 按天分区
CLUSTERED BY (user_id) SORTED BY (timestamp) INTO 1024 BUCKETS; -- 按用户ID分桶,桶内按时间排序
查询场景:
sql
-- 查询某天某个用户的详细行为
SELECT *
FROM user_behavior_optimized
WHERE dt = '2023-10-27' AND user_id = 123456;
-
分区查询:首先定位到
dt='2023-10-27'这个分区目录。 -
分桶查询:计算
user_id=123456的哈希值,直接定位到对应的那个桶文件(比如第 58 号桶)。 -
Hive 最终只需要扫描一个分区下的一个桶文件(可能只有几十MB),相比扫描数TB的全表,效率提升是指数级的!
5.分区vs分桶
| 维度 | 分区(Partition) | 分桶(Bucket) |
|---|---|---|
| 目的 | 减少 I/O(分区裁剪) | 优化 Join、采样、数据分布 |
| 依据 | 业务维度(如日期、地区) | 某一列的哈希值(如 user_id) |
| 物理结构 | 多级目录(/dt=xxx/region=yyy/) |
同一分区内多个文件(00000_0, 00001_0...) |
| 适用字段 | 低/中基数、常用于 WHERE 过滤 | 高基数、常用于 Join 或聚合的 Key |
| 文件数量 | 随分区值动态增长 | 固定(建表时指定) |
| 典型场景 | 日志按天分析、区域报表 | 大表 Join、用户行为分析、AB测试采样 |
-
优先分区:对于有明显时间维度或类别维度的表,首先考虑分区。
-
合理分桶 :当分区内数据量依然巨大,且需要高频进行
JOIN、GROUP BY、采样等操作时,再引入分桶。 -
先分区后分桶:分区是"宏观切分",分桶是"微观组织"。先用分区砍掉无关数据;再用分桶优化计算效率。
-
避免陷阱:警惕"过度分区"和"分桶列选择不当导致的数据倾斜"。