数据库分区表实战:Range/List/Hash 分区、分区裁剪与归档

深度讲解数据库分区表的设计与实战:Range/List/Hash/Key 四种分区方式、分区裁剪与分区选择、索引与分区关系、在线分区维护、冷热数据归档,以及与分库分表的取舍。

导语:分区表是"单表内"的分库分表

当一张表数据量到千万级、亿级,单一 B+ 树索引的随机 I/O 与锁竞争都会成为瓶颈。分区表在逻辑上是一张表,物理上是多个独立存储单元(分区)。查询若能命中单个分区(分区裁剪),扫描范围与锁粒度同时缩小,性能与运维都会改善。

一句话总结: 分区表解决的是「单表数据量巨大」的问题——通过分区裁剪把全表扫描缩小为局部扫描,并把冷热数据分开管理。


1. 分区表核心概念

1.1 逻辑表 vs 物理分区

逻辑视图:                         物理存储:
  users_log                        users_log#p20250101
  ├── 分区 p20250101  ←--分区间隔-->  ├── 数据文件
  ├── 分区 p20250201                users_log#p20250201
  ├── 分区 p20250301                  └── ...
  └── ...
  (对应用 SQL 透明)
  • 对业务透明:SQL 里照常写 users_log,优化器决定命中哪个分区。
  • DDL 隔离:可以对单个分区做 TRUNCATE PARTITION、DROP PARTITION,不影响其它分区。
  • 数据隔离:冷数据可以在独立分区上做压缩/归档,甚至 DETACH PARTITION。

1.2 分区 vs 分库分表(必须分清)

维度分区表分库分表
范围单实例内跨实例/跨库
复杂度低,透明高,需路由中间件
扩展性受单机上限约束可横向扩容
场景单表大、冷热分明数据量超单机、并发超高

分区表是在分库分表之前的自然演进。数据量在单机可承载范围(约数十亿行以内、实例存储 OK)时,优先用分区表;突破单机才上分库分表。


2. 四种分区方式

2.1 RANGE 分区(最常用)

按连续区间切分,天然适合时间、ID 等有序字段。

CREATE TABLE order_log (
    id BIGINT,
    created_at DATETIME,
    amount DECIMAL(10,2),
    PRIMARY KEY (id, created_at)     -- 分区键必须包含在唯一键/主键里
) PARTITION BY RANGE (TO_DAYS(created_at)) (
    PARTITION p2024q1 VALUES LESS THAN (TO_DAYS('2024-04-01')),
    PARTITION p2024q2 VALUES LESS THAN (TO_DAYS('2024-07-01')),
    PARTITION p2024q3 VALUES LESS THAN (TO_DAYS('2024-10-01')),
    PARTITION p2024q4 VALUES LESS THAN (TO_DAYS('2025-01-01')),
    PARTITION pFuture  VALUES LESS THAN MAXVALUE
);
  • 分区键必须参与主键/唯一键:MySQL 要求唯一索引包含分区键,否则无法保证跨分区唯一。
  • 好处:按时间归档、删除历史分区极快(DROP PARTITION 相当于删数据文件,比 DELETE 快无数倍)。

2.2 LIST 分区

按离散值列表切分,适合枚举/地域/状态。

CREATE TABLE region_stat (
    region_code CHAR(2),
    data JSON,
    PRIMARY KEY (region_code, id)
) PARTITION BY LIST (region_code) (
    PARTITION p_north VALUES IN ('BJ','TJ','HB'),
    PARTITION p_east  VALUES IN ('SH','JS','ZJ'),
    PARTITION p_other VALUES IN ('GZ','SC','GD')
);

2.3 HASH 分区

按哈希函数均匀散列,适合无天然分区字段、追求均匀分布的场景。

CREATE TABLE metric (
    id BIGINT,
    device_id BIGINT,
    value DOUBLE,
    PRIMARY KEY (id, device_id)
) PARTITION BY HASH (device_id) PARTITIONS 8;

2.4 KEY 分区

与 HASH 类似,但由 MySQL 内部哈希函数计算(支持多列),适合字符串等非数值键。

方式分区依据适用场景典型例子
RANGE连续区间时间、ID日志、订单按时间
LIST离散枚举地域、状态按区域/状态分区
HASH哈希散列均匀分布设备/用户 ID
KEYMySQL 内部哈希多列/字符串通用散列

一句话总结: RANGE 最常用(时间归档)、LIST 按枚举、HASH/KEY 求均匀;分区键必须进主键/唯一键。


3. 分区裁剪:让查询只碰一个分区

3.1 什么是分区裁剪

优化器根据 WHERE 条件中的分区键,提前排除不相关分区,只扫描可能命中的分区。分区裁剪失败时,等于退化回全表扫描。

-- ✅ 命中 p2024q3,只扫该分区(裁剪生效)
SELECT * FROM order_log
WHERE created_at >= '2024-07-01' AND created_at < '2024-10-01';

-- ❌ 裁剪失效:分区键上没有可推导的条件
SELECT * FROM order_log WHERE status = 'PAID';

3.2 让裁剪生效的写法

□ WHERE 中直接对分区键写范围/等值条件
□ 避免对分区键使用函数(TO_DAYS(created_at) 在列上 → 裁剪失效)
□ 传参尽量用具体的日期字面量/强类型,少用隐式转换
□ JOIN/子查询中涉及分区键时,条件尽量下推
□ 用 EXPLAIN PARTITIONS 验证实际命中了哪些分区
EXPLAIN PARTITIONS SELECT * FROM order_log WHERE created_at >= '2024-08-01'\G
-- 期望看到 partitions: p2024q3,p2024q4

一句话总结: 分区表收益的前提是裁剪生效;把分区键条件写"裸列 + 范围",别包函数、别做隐式转换。


4. 索引与分区:分区内索引 vs 全局索引

4.1 分区内索引(Local Index,MySQL 默认)

每个分区内部各建一套 B+ 树。查询先在裁剪后的分区内使用索引,再回表。好处是 DDL 局部化,坏处是全局唯一性约束受限。

分区内索引(默认):
  p2024q1: idx_user (B+树)  + 数据
  p2024q2: idx_user (B+树)  + 数据

唯一性约束:
  - 唯一索引必须包含分区键(否则全局唯一无法保证)
  - 所以 username 全局唯一 + 按 created_at 分区 → 做不到!

4.2 需要全局唯一怎么办

若业务需要「非分区键」全局唯一(如唯一手机号),而表又按时间分区,必须把该唯一列作为分区键的一部分,或用唯一索引包含分区键。

-- 手机号唯一 + 时间分区:把 phone 放进主键/唯一键
CREATE TABLE user_account (
    phone CHAR(11),
    created_at DATETIME,
    ...
    PRIMARY KEY (phone, created_at)
) PARTITION BY RANGE (TO_DAYS(created_at)) (...);

一句话总结: 分区表的唯一约束与分区键强绑定;全局唯一需要把该列并入唯一键。


5. 分区维护与冷热归档

5.1 在线分区维护操作

-- 新增分区(为未来数据预留)
ALTER TABLE order_log ADD PARTITION (PARTITION p2025q1 VALUES LESS THAN (TO_DAYS('2025-04-01')));

-- 删除历史分区(比 DELETE 快得多)
ALTER TABLE order_log DROP PARTITION p2024q1;

-- 清空分区(保留结构)
ALTER TABLE order_log TRUNCATE PARTITION p2024q2;

-- 合并/重建分区(InnoDB 需重建以回收空间)
ALTER TABLE order_log REBUILD PARTITION p2024q3;

-- 交换分区(把数据快速挪入/挪出)
ALTER TABLE order_log EXCHANGE PARTITION p2024q1 WITH TABLE order_log_archive;

5.2 时间分区 + 自动归档最佳实践

推荐组合:
  1. 按天/按季建分区
  2. 定时任务提前建未来 N 个分区(如提前 30 天)
  3. 到期的历史分区 DETACH/导出到归档库
  4. 归档完成后再 DROP,避免长时间占用空间

用分区实现"秒删历史数据",比 DELETE 大表高效一个数量级。

一句话总结: 分区维护围绕 ADD/DROP/TRUNCATE/EXCHANGE 展开,冷热归档是分区表最大的运维红利。


6. 分区表避坑清单

坑表现对策
分区键不进主键建表报错 / 唯一性被破坏分区键并入主键/唯一键
裁剪失效明明分区了还全表扫WHERE 写裸列 + 范围条件
分区键用函数TO_DAYS(col) 裁剪失效用具体日期范围
分区过多每分区独立句柄,开销大单表分区数控制在 1024 以内,常用 8~128
分区分完仍需全局唯一非分区键唯一做不到唯一列并入分区键
HASH 分区数量后改迁移代价大建表时定好 2^n 个分区
忽略 EXPLAIN PARTITIONS误以为裁剪生效每关键查询验证 partitions 列

7. 总结

分区表是数据库数据治理的重要工具:

环节要点
选型单机承载量内优先分区,超限才分库分表
分区键必须进主键/唯一键,优先选时间/RANGE
裁剪裸列 + 范围条件,EXPLAIN PARTITIONS 验证
索引分区内索引为主,全局唯一绑定分区键
运维提前建分区、到期归档 DROP,实现秒级清理

落地记住五件事:选对分区方式、分区键并入主键、保证裁剪生效、唯一约束绑分区键、冷热归档用分区维护。把分区表用好,千万级单表也能保持稳定的查询与运维节奏。

延伸阅读

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「database」更多文章

  1. 数据库安全加固与审计实战:权限最小化、加密、脱敏与合规
  2. 数据库容量规划与资源治理:从评估、监控到扩展路径
  3. 数据库字符集、排序规则与乱码实战:utf8mb4、Collation 选择与排查