今天聊聊Hive分区与分桶。做数据仓库的人,几乎每天都要和这两个概念打交道,但真正能把分区粒度选准、分桶数量定好、动态分区参数一次配明白的人,说实话不多。我经常遇到同事来问:为什么我的Hive表加了分区还是慢?为什么增量导入之后目录下全是几十KB的小文件?这些坑我都踩过一轮,这篇文章就把分区和分桶从原理到实战一次性讲透。内容偏向实际落地,适合数仓开发、数据工程师和正在学习Hive的读者参考,看完能直接拿自己项目里的表做一次体检和优化。
这里先明确一点:Hive里的“分区”是表数据的逻辑切分,和操作系统磁盘分区的概念完全是两码事。别搞混。Hive分区在HDFS上表现为不同目录,查询时通过分区裁剪只读需要的目录;分桶则是把数据按哈希散列到固定数量的文件或桶中。两者都是大数据存储优化的重要手段,但解决的不是同一个问题。
1. 用分区表给数据做一次“抽屉式”收纳
1.1 分区的本质:目录即过滤条件
Hive分区表的物理呈现形式很简单:在表目录之下,每个分区值对应一个独立的目录。比如一张订单表按日期dt分区,建表后HDFS上会长成这样的结构:
text复制/user/hive/warehouse/dwd.db/dwd_order_detail/
├── dt=2024-06-01/
│ ├── part-00000-xxxx
│ └── part-00001-xxxx
└── dt=2024-06-02/
├── part-00000-xxxx
└── part-00001-xxxx
查询时如果带上WHERE dt='2024-06-01',Hive并不会傻乎乎扫整个表,而是直接定位到dt=2024-06-01这个目录,只读里面的文件。这就是分区裁剪(Partition Pruning)的核心逻辑。
打个比方,这就好比你去图书馆找一本2024年出版的某类书。如果图书馆按类别分架,你只需要走到对应书架那一层;如果没有分架,你得把整个图书馆的书都翻一遍。分区表就是给数据提前做了书架分类,代价是维护成本高一丢丢,收益是查询扫描量指数级下降。
建表时用PARTITIONED BY声明分区字段,建表语法是这样的:
sql复制CREATE TABLE dwd_order_detail (
order_id BIGINT,
user_id BIGINT,
amount DECIMAL(10,2),
pay_status TINYINT
)
PARTITIONED BY (dt STRING)
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t';
注意分区字段dt只出现在PARTITIONED BY里,不能出现在表字段列表里,否则会报重复定义。在MySQL那种OLTP库里你会觉得加个日期字段很自然,但Hive里分区字段是独立于普通列之外的一个维度,普通列管数据内容,分区列管数据位置。
1.2 静态分区、动态分区和混合分区怎么选
实际写入数据时,分区有三种玩法:静态分区、动态分区、混合分区。
静态分区是手动指定分区值,每次写数据前你已经知道这波数据属于哪个分区:
sql复制INSERT OVERWRITE TABLE dwd_order_detail PARTITION(dt='2024-06-01')
SELECT order_id, user_id, amount, pay_status
FROM tmp_order_ods
WHERE order_date = '2024-06-01';
优点是执行计划明确,不会误写分区;缺点是一次只能写一个分区。如果业务上每天跑批量任务,你写2个静态分区就够了;可要是补数据补30天,就得写30条SQL,烦不烦?这时候动态分区就派上用场了。
动态分区可以不指定分区值,让Hive根据查询结果自动判断数据该进哪个分区,分区字段的值直接取SELECT出来的最后一列:
sql复制SET hive.exec.dynamic.partition=true;
SET hive.exec.dynamic.partition.mode=nonstrict;
INSERT OVERWRITE TABLE dwd_order_detail PARTITION(dt)
SELECT order_id, user_id, amount, pay_status, order_date
FROM tmp_order_ods;
注意我写了两个SET参数。hive.exec.dynamic.partition当然要开;更关键的是第二个参数hive.exec.dynamic.partition.mode,默认值是strict,在严格模式下,你必须至少指定一个静态分区,不允许只靠动态分区写入。改成nonstrict之后才允许全动态分区。
这两个参数在Hive CLI、Beeline里每次会话都要重新设置,别指望改了配置文件一劳永逸。如果你在调度平台跑HiveSQL,建议在脚本头部固定写上这两个参数,别省这几行。
混合分区则是前面静态后面动态,比如按天加省份两级分区时,把日期写死,省份走动态:
sql复制INSERT OVERWRITE TABLE dwd_order_detail PARTITION(dt='2024-06-01', province)
SELECT order_id, user_id, amount, pay_status, order_province
FROM tmp_order_ods
WHERE order_date = '2024-06-01';
这里有个硬性规则:静态分区必须放在动态分区
