导语:分区表是"单表内"的分库分表
当一张表数据量到千万级、亿级,单一 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 |
| KEY | MySQL 内部哈希 | 多列/字符串 | 通用散列 |
一句话总结: 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,实现秒级清理 |
落地记住五件事:选对分区方式、分区键并入主键、保证裁剪生效、唯一约束绑分区键、冷热归档用分区维护。把分区表用好,千万级单表也能保持稳定的查询与运维节奏。
延伸阅读
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。