分区类型详解: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 复杂业务

分区类型选择决策流程

说了这么多,到底怎么选?我画了一张流程图,帮你理清思路:

分区类型选择决策流程 开始选择分区类型 是否有自然的分区键? (时间、区域、类别等业务字段) 是 分区键是连续值还是离散值? (连续:时间/ID;离散:区域/状态) 连续 离散 RANGE 分区 按区间切分 LIST 分区 按离散值切分 否 是否需要数据均匀分布? (避免数据倾斜) 是 否 HASH 分区 哈希打散 COMPOSITE 分区 组合策略 提示:复合分区适合复杂业务,但管理成本高。建议先从简单分区开始,性能不足时再考虑复合。

这张图的核心逻辑很简单:有自然分区键就优先用 RANGE 或 LIST,没有就用 HASH,复杂场景才考虑复合。别一上来就搞复合分区,那是给自己找麻烦。

最后说一句:分区类型选错了,后期改起来非常痛苦。我见过一个项目,上线半年后才发现 HASH 分区不适合范围查询,但数据量已经 10TB 了,重建分区表花了整整一个周末。所以,前期多花点时间分析业务查询模式,比后期填坑划算得多。