30、MySQL分区表:RANGE分区、LIST分区、HASH分区、分区管理

分区表这个话题,说实话,我早年做项目时一直有点「敬而远之」。总觉得单表能搞定的事,何必搞复杂?直到有一次,我接手了一个日志系统——单表数据量到了2亿行,查询慢得像蜗牛爬,DBA天天催我优化。嗯,那次之后,我才真正把分区表当回事。

说白了,分区就是把一张大表,按某种规则拆成多个物理小表。但对应用层来说,它还是一张表。你写SQL时完全不用关心数据到底存在哪个分区里,MySQL自己会处理。这就像你把一个大仓库分成几个小隔间,找东西时只翻一个隔间就行,效率自然高。

RANGE分区:按范围切分

RANGE分区是我用得最多的分区方式。它按连续区间把数据分到不同分区。最适合处理时间序列数据,比如订单表、日志表。

看个例子:

CREATE TABLE orders (
    order_id INT NOT NULL,
    customer_id INT,
    order_date DATE,
    amount DECIMAL(10,2)
)
PARTITION BY RANGE (YEAR(order_date)) (
    PARTITION p2019 VALUES LESS THAN (2020),
    PARTITION p2020 VALUES LESS THAN (2021),
    PARTITION p2021 VALUES LESS THAN (2022),
    PARTITION p_future VALUES LESS THAN MAXVALUE
);

这里有个细节要注意:VALUES LESS THAN 定义的是上限,不包含边界值。比如2020年的数据会进p2020分区,而不是p2019。我刚开始用时就搞反过,查了半天数据去哪了。

MAXVALUE是个特殊值,代表正无穷。所有超出前面分区范围的数据,都会落进这个分区。我个人习惯是,如果业务数据有明确的上限,就别用MAXVALUE。为什么?因为一旦数据进了MAXVALUE分区,后续你想再拆分它,就得做分区合并,操作起来比较麻烦。

LIST分区:按枚举值切分

LIST分区跟RANGE不同,它不是按连续区间,而是按离散值列表来划分。适合那种分类明确的场景,比如按地区、按状态。

CREATE TABLE customer (
    cust_id INT NOT NULL,
    name VARCHAR(50),
    region_code INT
)
PARTITION BY LIST (region_code) (
    PARTITION p_north VALUES IN (1, 3, 5),
    PARTITION p_south VALUES IN (2, 4, 6),
    PARTITION p_other VALUES IN (7, 8, 9)
);

注意,LIST分区没有MAXVALUE这种兜底方案。如果你插入的数据不在任何分区的值列表里,MySQL会直接报错。我在项目中遇到过这种情况:业务新增了一个地区编码,结果插入失败,排查了半天才发现是分区定义没更新。

避坑指南:LIST分区一定要考虑未来可能新增的值。要么在定义时预留一个「其他」分区,要么定期检查并更新分区定义。

HASH分区:按哈希值均匀分布

HASH分区跟前面两种思路完全不同。它不关心数据的业务含义,只关心数据能不能均匀分布。你指定一个分区数量,MySQL对分区键做哈希运算,然后取模决定数据去哪个分区。

CREATE TABLE user_session (
    session_id INT NOT NULL,
    user_id INT,
    login_time DATETIME
)
PARTITION BY HASH (user_id)
PARTITIONS 4;

这里分成了4个分区。MySQL内部会计算 MOD(哈希值, 4) 来决定数据归属。

HASH分区的好处是数据分布比较均匀,不会出现某个分区数据特别多、其他分区很空的情况。但缺点也很明显:你没法控制某个具体数据落在哪个分区。而且,如果你选的分区键不够分散(比如性别字段只有两个值),那哈希取模后数据还是会扎堆。

我记得有一次,同事用时间戳做HASH分区键,结果因为时间戳是递增的,取模后数据全挤在最后几个分区里。嗯,HASH分区适合那种键值本身分布就比较均匀的场景。

分区管理:增删改查

分区不是建完就完事了。业务在变,数据在涨,分区也得跟着调整。下面这几个操作,我几乎每个项目都会用到。

添加分区

-- RANGE分区添加新分区
ALTER TABLE orders ADD PARTITION (
    PARTITION p2022 VALUES LESS THAN (2023)
);

-- HASH分区增加分区数量
ALTER TABLE user_session ADD PARTITION PARTITIONS 2;

RANGE分区添加时要注意:新分区的范围必须大于当前最大分区。HASH分区添加时,MySQL会重新计算所有数据的哈希值并移动数据——这个操作在数据量大时非常耗时,建议在业务低峰期执行。

删除分区

ALTER TABLE orders DROP PARTITION p2019;

删除分区会同时删除分区里的所有数据。这个操作比 DELETE FROM 快得多,因为它直接删除底层文件。我经常用这个特性来做数据归档——把旧数据所在的分区直接删掉,比逐条删除快几个数量级。

注意:删除分区不可逆。如果你只是想清空数据但保留分区结构,应该用 TRUNCATE PARTITION

合并与拆分分区

-- 合并两个RANGE分区
ALTER TABLE orders REORGANIZE PARTITION p2019, p2020 INTO (
    PARTITION p_old VALUES LESS THAN (2021)
);

-- 拆分一个分区为两个
ALTER TABLE orders REORGANIZE PARTITION p2021 INTO (
    PARTITION p2021_h1 VALUES LESS THAN (2021-07-01),
    PARTITION p2021_h2 VALUES LESS THAN (2022-01-01)
);

REORGANIZE 是个很灵活的操作。它不删除数据,只是重新划分数据的分区归属。但要注意,这个操作会锁表,数据量大时可能影响线上业务。

分区表的核心限制

分区表不是万能的。我列几个常见的坑:

  • 分区键必须包含在主键或唯一键中。这是MySQL的硬性要求。如果你表里有主键,分区键必须是主键的一部分。
  • 外键不支持分区表。分区表不能有外键约束,也不能被其他表的外键引用。
  • 分区数量建议不超过1024个。分区太多反而会降低性能,因为MySQL要维护每个分区的元数据。
  • 查询条件不带分区键时,MySQL会扫描所有分区。这比普通全表扫描还慢,因为要打开多个分区文件。
我的经验:分区表最适合「写多读少」且「按时间范围查询」的场景。如果你的查询条件经常变化,或者经常做跨分区聚合,分区表可能帮倒忙。

知识体系总览

下面这张图,我把分区表的核心知识点串起来了。你可以对照着回顾一下:

MySQL分区表知识体系 分区表 RANGE 分区 LIST 分区 HASH 分区 KEY 分区 分区管理操作 ADD PARTITION DROP PARTITION TRUNCATE PARTITION REORGANIZE PARTITION 核心限制与注意事项 • 分区键必须包含在主键或唯一键中 • 不支持外键约束 • 分区数建议 ≤ 1024

分区表是个好工具,但别滥用。我见过有人把只有几千行的表也分了区,结果查询反而变慢了。记住一句话:分区是为了解决大表问题,不是为了让你的建表语句看起来更酷。

实际项目中,我一般这样判断要不要分区:单表数据量超过1000万行,或者有明显的「冷热数据」分层(比如最近三个月的数据经常查,更早的数据几乎不查),这时候分区就很合适。否则,老老实实用单表加索引,反而更省心。


公众号:蓝海资料掘金营,微信deep3321