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分区,我总结了几条避坑指南:

  1. 枚举值必须全覆盖——插入的数据如果不在任何分区的值列表里,MySQL会直接报错。我建议加一个PARTITION p_other VALUES IN (DEFAULT)兜底,或者用PARTITION BY LIST COLUMNS配合DEFAULT分区(MySQL 8.0+支持)。
  2. 值列表不能太大——每个分区VALUES IN (...)里的值太多,分区裁剪效率会下降。我一般控制在20个以内。
  3. 分区数不要超过100个——虽然MySQL允许1024个分区,但超过100个后,DDL操作(比如加分区、删分区)会变得很慢。我曾经维护过一个200个分区的表,加一个分区要等5分钟。
  4. 注意分区键的数据倾斜——比如订单状态里,「已完成」的数据量可能是「待支付」的100倍。这时候可以考虑把大分区再拆成子分区,或者用复合分区策略。

4.6 List分区 vs 其他分区方式

我经常被问到:「List分区和Range分区到底怎么选?」这里给个对比表:

对比维度 List分区 Range分区 Hash分区
分区键类型 离散值(枚举) 连续值(范围) 任意类型(取模)
分区映射方式 显式指定 区间边界 哈希计算
分区裁剪效率 高(精确匹配) 高(范围匹配) 中(需计算哈希)
数据倾斜风险 高(需人工均衡) 中(边界设计) 低(自动分布)
适用场景 状态、地域、类型 时间、ID范围 均匀分布的数据

我的选择原则很简单:如果分区键是枚举值,且你知道每个值对应多少数据量,用List分区;如果数据是连续增长的,用Range分区;如果数据分布均匀且你懒得管,用Hash分区。

4.7 核心知识体系

下面这张图总结了List分区的核心设计思路,我画出来方便你理解:

List分区核心设计思路 List分区 离散枚举值映射 显式分区定义 分区裁剪优化 订单状态分区 地域数据隔离 业务类型分类 注意事项:值全覆盖 | 分区数控制 | 数据倾斜 | 主键包含分区键 核心原则 枚举值有限且稳定 → List分区是首选 数据倾斜严重 → 考虑复合分区或重新设计 未来可能新增枚举值 → 预留兜底分区

4.8 总结

List分区是处理枚举值数据的利器。它的优势在于精准映射和高效裁剪——你查哪个状态,MySQL就只扫哪个分区。但要注意,它不适合枚举值太多或频繁变化的场景。

我个人建议:先分析业务数据的枚举值分布,再决定是否用List分区。如果枚举值不超过50个,且查询模式集中在少数几个值上,List分区能给你带来立竿见影的性能提升。如果枚举值会持续增长,或者数据分布极度不均匀,那就要考虑其他分区方式了。