4、List分区实战:基于枚举值的分区设计(按地区、状态字段分区)
聊完了Range和Hash分区,咱们来看看List分区。说实话,这是我在实际项目里用得最多的分区方式。为什么?因为业务数据天生就带着「分类」属性——订单状态、用户地域、商品类目,这些都是典型的枚举值。
List分区的核心思想很简单:把指定列的值映射到特定分区。你告诉MySQL:「北京、上海、广州的数据放分区1,其他城市放分区2」,它就照做。不像Range需要定义区间边界,也不像Hash需要计算哈希值。
4.1 什么是List分区?
List分区,说白了就是基于离散值的显式映射。每个分区对应一组明确的值列表。
语法上长这样:
CREATE TABLE orders (
id INT NOT NULL,
order_id VARCHAR(32),
status TINYINT, -- 1:待支付 2:已支付 3:已发货 4:已完成 5:已取消
created_at DATETIME
)
PARTITION BY LIST (status) (
PARTITION p_pending VALUES IN (1),
PARTITION p_paid VALUES IN (2),
PARTITION p_shipped VALUES IN (3),
PARTITION p_completed VALUES IN (4),
PARTITION p_cancelled VALUES IN (5)
);
注意,List分区用的是VALUES IN (...),不是VALUES LESS THAN。每个值只能出现在一个分区里,不能重复映射。
关键区别:Range分区处理的是连续区间,List分区处理的是离散集合。如果你的数据列只有有限个枚举值,List分区是天然的选择。
4.2 适用场景:什么时候该用List分区?
我总结了三类典型场景,你在做技术选型时可以对照看看:
| 场景 | 分区键示例 | 典型分区数 | 我为什么推荐 |
|---|---|---|---|
| 状态字段 | 订单状态、审批状态、任务状态 | 5-10个 | 状态查询频率高,分区裁剪效果好 |
| 地域字段 | 省份、城市、国家代码 | 10-50个 | 按区域隔离数据,方便数据治理 |
| 业务类型 | 支付方式、商品类目、渠道来源 | 5-20个 | 不同类型数据量差异大,可独立管理 |
我个人习惯是:只要分区键的枚举值不超过50个,且值相对稳定,List分区就是首选。超过50个?那得考虑Hash分区或者重新设计分区策略了。
4.3 实战案例:按订单状态分区
拿电商系统举例。订单表是典型的大表,一个月几千万条很常见。而且查询模式高度集中在「待支付」和「已发货」这两个状态上——运营要看待支付订单催付,客服要查已发货订单跟踪物流。
我之前接手过一个项目,订单表没分区,每天几百万条数据,WHERE status = 1 的查询动不动就全表扫描。后来改成List分区,效果立竿见影。
建表语句:
CREATE TABLE order_list (
id BIGINT NOT NULL AUTO_INCREMENT,
order_no VARCHAR(32) NOT NULL,
user_id BIGINT NOT NULL,
status TINYINT NOT NULL COMMENT '1待支付 2已支付 3已发货 4已完成 5已取消 6售后中',
amount DECIMAL(10,2),
created_at DATETIME NOT NULL,
PRIMARY KEY (id, status),
INDEX idx_user_id (user_id),
INDEX idx_created_at (created_at)
)
PARTITION BY LIST (status) (
PARTITION p_pending VALUES IN (1),
PARTITION p_paid VALUES IN (2),
PARTITION p_shipped VALUES IN (3),
PARTITION p_completed VALUES IN (4),
PARTITION p_cancelled VALUES IN (5),
PARTITION p_refund VALUES IN (6)
);
一个小技巧:主键里带上分区键 PRIMARY KEY (id, status)。MySQL要求分区键必须是主键或唯一键的一部分。这个限制很多人踩过坑,我刚开始也忘了加,建表直接报错。
查询效果对比:
-- 改分区前:全表扫描,扫描几千万行
EXPLAIN SELECT * FROM order_list WHERE status = 1 AND created_at > '2024-01-01';
-- 改分区后:只扫描p_pending分区,数据量减少80%
-- 分区裁剪生效,查询速度提升5-10倍
4.4 按地域分区:数据治理的好帮手
按地域分区,我遇到过最典型的场景是数据合规。有些业务要求不同地区的数据存储策略不同——比如欧盟的数据要单独存储,国内的数据要保留更长时间。
举个例子:
CREATE TABLE user_profile (
user_id BIGINT NOT NULL,
region VARCHAR(10) NOT NULL COMMENT 'CN:中国 US:美国 EU:欧盟 OTHER:其他',
nickname VARCHAR(50),
email VARCHAR(100),
created_at DATETIME
)
PARTITION BY LIST COLUMNS (region) (
PARTITION p_cn VALUES IN ('CN'),
PARTITION p_us VALUES IN ('US'),
PARTITION p_eu VALUES IN ('EU'),
PARTITION p_other VALUES IN ('OTHER')
);
注意这里用了PARTITION BY LIST COLUMNS,不是PARTITION BY LIST。区别在于:LIST COLUMNS支持字符串、日期等非整数类型,而且可以指定多个列。我个人建议能用LIST COLUMNS就用它,灵活性高很多。
曾经踩过的坑:有一次我把地域分区键设计成country_code(整数类型),结果业务扩展到了200多个国家。List分区最多支持1024个分区,但200个分区管理起来已经非常痛苦了。后来我改成按大洲分区,每个大洲再内部做子分区。嗯,设计分区策略时一定要考虑未来扩展性。
4.5 List分区的注意事项
用了这么多年List分区,我总结了几条避坑指南:
- 枚举值必须全覆盖——插入的数据如果不在任何分区的值列表里,MySQL会直接报错。我建议加一个
PARTITION p_other VALUES IN (DEFAULT)兜底,或者用PARTITION BY LIST COLUMNS配合DEFAULT分区(MySQL 8.0+支持)。 - 值列表不能太大——每个分区
VALUES IN (...)里的值太多,分区裁剪效率会下降。我一般控制在20个以内。 - 分区数不要超过100个——虽然MySQL允许1024个分区,但超过100个后,DDL操作(比如加分区、删分区)会变得很慢。我曾经维护过一个200个分区的表,加一个分区要等5分钟。
- 注意分区键的数据倾斜——比如订单状态里,「已完成」的数据量可能是「待支付」的100倍。这时候可以考虑把大分区再拆成子分区,或者用复合分区策略。
4.6 List分区 vs 其他分区方式
我经常被问到:「List分区和Range分区到底怎么选?」这里给个对比表:
| 对比维度 | List分区 | Range分区 | Hash分区 |
|---|---|---|---|
| 分区键类型 | 离散值(枚举) | 连续值(范围) | 任意类型(取模) |
| 分区映射方式 | 显式指定 | 区间边界 | 哈希计算 |
| 分区裁剪效率 | 高(精确匹配) | 高(范围匹配) | 中(需计算哈希) |
| 数据倾斜风险 | 高(需人工均衡) | 中(边界设计) | 低(自动分布) |
| 适用场景 | 状态、地域、类型 | 时间、ID范围 | 均匀分布的数据 |
我的选择原则很简单:如果分区键是枚举值,且你知道每个值对应多少数据量,用List分区;如果数据是连续增长的,用Range分区;如果数据分布均匀且你懒得管,用Hash分区。
4.7 核心知识体系
下面这张图总结了List分区的核心设计思路,我画出来方便你理解:
4.8 总结
List分区是处理枚举值数据的利器。它的优势在于精准映射和高效裁剪——你查哪个状态,MySQL就只扫哪个分区。但要注意,它不适合枚举值太多或频繁变化的场景。
我个人建议:先分析业务数据的枚举值分布,再决定是否用List分区。如果枚举值不超过50个,且查询模式集中在少数几个值上,List分区能给你带来立竿见影的性能提升。如果枚举值会持续增长,或者数据分布极度不均匀,那就要考虑其他分区方式了。