分区类型详解:RANGE、LIST、HASH 与复合分区
分区类型的选择,说白了就是决定数据怎么“分堆”存放。选对了,查询快得像切黄油;选错了,分区表反而比普通表还慢。我见过不少团队,一上来就拍脑袋选 RANGE,结果数据倾斜严重,某些分区撑爆了,某些分区空荡荡。
这一章,咱们把四种核心分区类型掰开揉碎讲清楚。每种我都会结合实战经验,告诉你什么时候用、怎么用、坑在哪。
范围分区(RANGE)
RANGE 分区是最直观的一种。按某个列的连续区间划分,比如时间、ID 范围。我个人习惯把它叫做“按段切分”。
适用场景:时间序列数据、自增主键、有序数值。
看个例子,按订单日期分区:
CREATE TABLE orders (
order_id INT,
order_date DATE,
amount DECIMAL(10,2)
)
PARTITION BY RANGE (YEAR(order_date)) (
PARTITION p_2021 VALUES LESS THAN (2022),
PARTITION p_2022 VALUES LESS THAN (2023),
PARTITION p_2023 VALUES LESS THAN (2024),
PARTITION p_future VALUES LESS THAN MAXVALUE
);
这里有个关键点:VALUES LESS THAN 定义的是上界,不包含边界值。比如 2022-01-01 会落入 p_2022,而不是 p_2021。
我曾经踩过的坑:MAXVALUE 分区一定要留。有一次我忘了加,业务数据提前进入下一年,插入直接报错。生产环境半夜三点被叫起来加分区,那滋味……嗯,你懂的。
RANGE 分区的优势在于分区裁剪效率极高。查询条件里带上分区键的范围,优化器能直接跳过无关分区。比如 WHERE order_date BETWEEN '2022-06-01' AND '2022-06-30',只会扫描 p_2022 一个分区。
列表分区(LIST)
LIST 分区是按离散值来划分的。数据不是连续的区间,而是固定的几个值。比如地区、状态码、产品类别。
我做过一个电商项目,订单按区域分:
CREATE TABLE orders (
order_id INT,
region VARCHAR(20),
amount DECIMAL(10,2)
)
PARTITION BY LIST (region) (
PARTITION p_north VALUES IN ('北京','天津','河北'),
PARTITION p_south VALUES IN ('广东','广西','海南'),
PARTITION p_east VALUES IN ('上海','江苏','浙江'),
PARTITION p_other VALUES IN ('其他')
);
LIST 分区有个硬性要求:每个值必须且只能属于一个分区。如果你插入一个没定义的值,比如 '西藏',数据库会直接报错。
我的建议:永远留一个 DEFAULT 或 OTHERS 分区兜底。虽然 MySQL 原生 LIST 不支持 DEFAULT,但你可以用 VALUES IN ('其他') 模拟。Oracle 和 PostgreSQL 支持 DEFAULT 分区,更灵活。
LIST 分区的裁剪逻辑也很直接。查询条件 WHERE region = '广东',优化器直接定位到 p_south 分区。但要注意,如果条件里用了 IN ('广东','上海'),会扫描两个分区,性能依然不错。
哈希分区(HASH)
HASH 分区跟 RANGE、LIST 完全不同。它不是按业务语义分,而是通过哈希函数把数据均匀打散到各个分区。说白了,就是“随机分堆”。
为什么要用 HASH?因为有些场景下,数据没有天然的分区键。比如用户 ID 是随机生成的,按 ID 范围分会导致数据倾斜。这时候 HASH 分区就派上用场了。
CREATE TABLE users (
user_id INT,
user_name VARCHAR(50),
reg_date DATE
)
PARTITION BY HASH (user_id)
PARTITIONS 8;
这里 PARTITIONS 8 表示分成 8 个分区。数据库会对 user_id 取哈希值,然后模 8,决定数据落到哪个分区。
关键点:分区数最好是 2 的幂次方(2、4、8、16……)。为什么?因为哈希取模运算在 2 的幂次方下效率最高,而且数据分布更均匀。我习惯用 8 或 16 起步,后续扩容也方便。
HASH 分区的缺点也很明显:分区裁剪能力弱。查询条件里如果只带 WHERE user_id = 12345,优化器能算出哈希值,直接定位到某个分区。但如果条件里没有分区键,比如 WHERE user_name = '张三',那就得扫描所有分区,性能反而更差。
注意:HASH 分区不适合范围查询。比如 WHERE user_id BETWEEN 1000 AND 2000,优化器无法确定每个 ID 的哈希值落在哪个分区,只能全扫。你想想看,这跟没分区有什么区别?
复合分区(COMPOSITE)
复合分区,也叫子分区。就是把两种分区策略叠加起来用。先按一种方式分大区,再在每个大区里按另一种方式分子区。
我做过一个日志系统,数据量巨大。先按年做 RANGE 分区,再按月做子分区。这样既能快速裁剪年份,又能精细管理月份数据。
CREATE TABLE logs (
log_id INT,
log_date DATE,
log_level VARCHAR(10),
message TEXT
)
PARTITION BY RANGE (YEAR(log_date))
SUBPARTITION BY HASH (MONTH(log_date))
SUBPARTITIONS 12 (
PARTITION p_2022 VALUES LESS THAN (2023),
PARTITION p_2023 VALUES LESS THAN (2024),
PARTITION p_2024 VALUES LESS THAN (2025)
);
这个例子中,每年一个大分区,每个大分区里又按月份哈希分成 12 个子分区。查询 WHERE log_date BETWEEN '2023-06-01' AND '2023-06-30',先定位到 p_2023 分区,再通过哈希定位到对应的子分区,裁剪效率非常高。
复合分区的组合方式有很多种:
| 主分区类型 | 子分区类型 | 典型场景 |
|---|---|---|
| RANGE | HASH | 时间范围 + 均匀分布 |
| RANGE | LIST | 时间范围 + 区域分类 |
| LIST | HASH | 区域分类 + 均匀分布 |
| LIST | RANGE | 区域分类 + 时间范围 |
我的经验:复合分区不是越多越好。子分区数量建议控制在 50 以内。太多子分区会导致元数据膨胀,DDL 操作变慢。我曾经试过 1000 个子分区,结果 ALTER TABLE 加个列都要跑 10 分钟……
四种分区类型对比
为了方便你快速决策,我整理了一张对比表:
| 特性 | RANGE | LIST | HASH | COMPOSITE |
|---|---|---|---|---|
| 分区依据 | 连续区间 | 离散值列表 | 哈希函数 | 组合策略 |
| 数据分布 | 按区间集中 | 按类别集中 | 均匀分布 | 可控分布 |
| 分区裁剪 | 极强 | 强 | 弱(仅等值) | 极强 |
| 范围查询 | 优秀 | 一般 | 差 | 优秀 |
| 等值查询 | 优秀 | 优秀 | 优秀 | 优秀 |
| 扩容难度 | 低 | 中 | 高 | 高 |
| 典型场景 | 时间序列 | 区域/状态 | 随机ID | 复杂业务 |
分区类型选择决策流程
说了这么多,到底怎么选?我画了一张流程图,帮你理清思路:
这张图的核心逻辑很简单:有自然分区键就优先用 RANGE 或 LIST,没有就用 HASH,复杂场景才考虑复合。别一上来就搞复合分区,那是给自己找麻烦。
最后说一句:分区类型选错了,后期改起来非常痛苦。我见过一个项目,上线半年后才发现 HASH 分区不适合范围查询,但数据量已经 10TB 了,重建分区表花了整整一个周末。所以,前期多花点时间分析业务查询模式,比后期填坑划算得多。