分区表
一、分区表的核心原理
1.1 逻辑表 vs 物理分区
- 逻辑表:用户通过
CREATE TABLE ... PARTITION BY ...创建的分区表,在information_schema中只有一条记录。 - 物理分区:每个分区对应一个独立的表空间文件(InnoDB 中是
.ibd文件,MyISAM 中是.MYD/.MYI)。数据按分区规则写入不同的物理文件。
当执行查询时,优化器会根据 WHERE 条件中的分区键,只扫描必要的分区,这个行为称为 分区裁剪(Partition Pruning)。
1.2 支持的存储引擎
- InnoDB(最常用,支持所有分区类型)
- MyISAM(已逐渐淘汰)
- NDB(用于 MySQL Cluster)
二、分区类型详解
MySQL 支持以下几种分区方式,每种方式适合不同的数据分布需求。
| 分区类型 | 语法 / 规则 | 适用场景 | 示例 |
|---|---|---|---|
| RANGE分区 | PARTITION BY RANGE(column) (PARTITION p1 VALUES LESS THAN (value), ...) |
时间序列数据、有序范围 | 按年份:VALUES LESS THAN (2020), LESS THAN (2021)… |
| LIST分区 | PARTITION BY LIST(column) (PARTITION p1 VALUES IN (value_list), ...) |
离散枚举值,如地区、状态 | 按省份:VALUES IN ('bj','sh'), VALUES IN ('gz','sz') |
| HASH分区 | PARTITION BY HASH(column) PARTITIONS n; |
均匀分散写入,避免热点 | 按用户ID:HASH(user_id) PARTITIONS 16 |
| KEY分区 | PARTITION BY KEY(column) PARTITIONS n; |
类似 HASH,但使用 MySQL 内部哈希函数,适用于字符串等非整数 | 按登录名:KEY(username) PARTITIONS 8 |
| 子分区(Subpartitioning) | SUBPARTITION BY HASH/KEY |
在一级分区内再次细分 | RANGE 按年,再 HASH 按用户ID,实现“年+随机分布” |
2.1 RANGE 分区示例
1 | CREATE TABLE orders ( |
2.2 LIST 分区示例
1 | CREATE TABLE sales ( |
注意:MySQL 5.5+ 支持
LIST COLUMNS和RANGE COLUMNS,可以直接使用多列或非整数类型作为分区键,例如VARCHAR、DATE。
2.3 HASH 分区示例
1 | CREATE TABLE user_log ( |
数据通过 MOD( HASH(user_id), 16 ) 分布在 16 个分区中。
三、分区表的运维管理
分区表的一大优势是 管理方便,尤其是对历史数据的清理和归档。
3.1 添加分区
1 | ALTER TABLE orders ADD PARTITION (PARTITION p2023 VALUES LESS THAN (2024)); |
3.2 删除分区(快速清空数据)
1 | ALTER TABLE orders DROP PARTITION p2019; |
DROP PARTITION比DELETE快几个数量级,并且几乎不产生 undo log,适合定期清理历史数据。
3.3 重组分区(拆分或合并)
1 | -- 将 p_future 拆分成两个分区 |
3.4 检查分区数据分布
1 | SELECT PARTITION_NAME, TABLE_ROWS, DATA_LENGTH |
四、分区表查询优化(关键)
4.1 分区裁剪(Partition Pruning)
当查询 WHERE 条件中包含 分区键 且使用了可确定范围的表达式时,优化器会跳过不相关的分区。
有效裁剪的例子:
1 | -- 只扫描 p2022 和 p2023 分区 |
无效裁剪(全表扫描所有分区)的例子:
1 | -- 对分区键使用函数,但无法反向推导出范围 |
技巧:尽量直接使用分区列进行范围比较,而不是在列上包裹函数。
4.2 聚合查询优化
如果每个分区的数据量不大,可以对每个分区进行聚合,然后再合并结果。例如:
1 | -- 按月分区,统计每年金额 |
优化器会自动对每个分区做局部聚合,最后汇总。
4.3 跨分区查询的代价
- 查询涉及多个分区时,需要扫描并合并结果,效率低于单分区查询。
- 如果 没有指定分区键,则会扫描 所有分区,性能可能比普通表还差(因为分区数量多导致上下文切换开销)。
建议:所有重要查询都应该带上分区键条件。
五、分区表的限制(非常重要)
| 限制项 | 说明 |
|---|---|
| 唯一索引(包括主键)必须包含分区键 | 如果表有主键或唯一键,那么分区键必须是这些键的一部分(可以是复合键之一)。例如主键是 (id, order_date),则分区键可以是 order_date。 |
| 不支持外键 | 分区表的外键约束不被支持,即使表是 InnoDB 引擎。 |
| 分区数量上限 | 最多 8192 个分区(MySQL 8.0 允许子分区后总分区数仍受限制)。 |
| 全文索引不支持分区 | MyISAM 可以但已废弃,InnoDB 不支持对分区表使用 FULLTEXT。 |
| 空间索引(GIS)不支持分区 | 不能对 GEOMETRY 列创建 SPATIAL 索引。 |
| 某些存储引擎的限制 | 例如 MERGE、CSV、FEDERATED 不支持分区。 |
| 锁定问题 | DDL 操作(如添加/删除分区)可能会锁表(取决于 MySQL 版本和算法,8.0 后有所改善)。 |
六、适用场景与典型案例
6.1 典型适用场景
- 时间序列日志 / 订单 / 事件表
- 数据量大,但查询通常集中在最近一段时间。
- 按时间 RANGE 分区,既能加速查询(裁剪到最近几个分区),又能轻松删除旧数据(
DROP PARTITION)。
- 大表的周期性归档
- 不需要使用分库分表,只需要按时间分区,每月或每年切换。
- 均匀分散写入热点(HASH / KEY 分区)
- 例如用户动作日志,用
user_id做 HASH 分区,避免写入集中在一个物理文件(降低 InnoDB 的 page 锁竞争)。
- 例如用户动作日志,用
6.2 不适合分区的场景
- 表很小(< 100 万行):分区不会带来性能提升,反而增加管理开销。
- 唯一索引无法包含分区键:例如订单表主键是
order_id,但你想按created_time分区,这不被允许(除非你把created_time加入主键)。 - 频繁跨分区查询:如果查询总是跨多个分区且无法带上分区键,性能会下降。
- 需要外键约束:分区表不支持外键。
6.3 真实案例
场景:电商订单表 orders,数据量 3 亿行,按年增长 6000 万行。业务查询主要是最近 3 个月的订单,同时需要保留 5 年数据,清理旧数据。
方案:按 order_date 按月 RANGE 分区。
- 查询
WHERE order_date > '2025-01-01'只扫描 5 个分区,速度极快。 - 删除 5 年前的数据:
ALTER TABLE orders DROP PARTITION p201901, p201902 ... - 每月新增分区:提前创建未来 2 个月的分区。
七、分区表 vs 分表(Sharding)
| 维度 | 分区表 | 分表(应用程序分片) |
|---|---|---|
| 对应用透明性 | ✅ 完全透明,SQL 不变 | ❌ 需要中间件或修改代码 |
| 跨分区查询 | 优化器自动处理,但效率可能下降 | 需要应用层聚合,实现复杂 |
| 跨库事务 | 不涉及,所有分区在同一实例 | 可能涉及分布式事务 |
| 扩展性 | 受限于单机磁盘 I/O 和 CPU | 可水平扩展到多台服务器 |
| 运维复杂度 | 低(MySQL 自带管理) | 高(需要中间件、数据迁移工具) |
| 适用规模 | 单机 TB 级数据,千万~亿级行 | 单机无法承受的超大规模(>5亿行、>5TB) |
结论:能先用分区表解决的,就不要上分库分表。分库分表是最后的手段,会引入极大的复杂性。
八、最佳实践与注意事项
- 分区键的选择
- 选择高选择性的列,使得数据尽量均匀分布(HASH 分区)或符合业务访问模式(RANGE 分区)。
- 如果表有主键 / 唯一键,分区键必须是这些键的子集。
- 分区数量不宜过多
- 建议每个表分区数不超过 100 个。太多分区会导致文件句柄过多、
INFORMATION_SCHEMA查询变慢。
- 建议每个表分区数不超过 100 个。太多分区会导致文件句柄过多、
- 定期管理分区
- 对于 RANGE 分区,提前创建未来分区(比如提前 2 个月),避免插入数据时因无对应分区而报错。
- 使用事件调度器(Event Scheduler)自动增加 / 删除分区。
九、分区表索引情况
当你对分区表创建索引(包括主键索引、唯一索引、二级索引)时,MySQL 会为每一个分区单独构建一份索引文件
- 分区裁剪:优化器先根据
WHERE条件确定需要扫描哪些分区。 - 独立索引查找:对每一个需要扫描的分区,使用该分区自己的索引结构进行检索。
- 结果合并:如果是跨分区的查询,还需要将各分区返回的结果合并(比如
UNION或ORDER BY需要归并)。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 夏天的风吹向哪里!
