range分区按列值连续区间分配数据,适合时间字段或自增主键的范围查询场景;需满足查询条件含分区键的等值或范围扫描才能实现分区裁剪,且分区键须为主键/唯一索引一部分。

什么是 RANGE 分区,它适合什么场景?
RANGE 分区按列值的连续区间把数据分到不同物理分区里,最常用于时间字段(如 created_at、dt)或自增主键。它不是万能的——只有查询条件中频繁用到分区键的等值或范围扫描时,才能真正跳过无关分区(即 partition pruning)。如果业务总查 user_id 但按 created_at 分区,性能反而可能更差,因为 MySQL 还得遍历所有分区找匹配行。
- 分区键必须是主键/唯一索引的一部分(否则建表报错
ERROR 1503 (HY000): A PRIMARY KEY must include all columns in the table's partitioning function) - 不支持对已存在数据的表直接添加 RANGE 分区;必须重建表(
ALTER TABLE ... PARTITION BY RANGE会触发全量拷贝) - 时间字段建议用
DATE或INT(如YYYYMMDD),避免用DATETIME带时分秒——精度太高会让分区边界难管理
如何安全地给大表加 RANGE 分区?
不能直接 ALTER TABLE t1 PARTITION BY RANGE (TO_DAYS(created_at)),尤其当表有上亿行时,锁表时间不可控,线上服务大概率超时。稳妥做法是“影子表 + 数据迁移 + 原子切换”:
- 先创建带分区的新表
t1_new,结构和索引与原表一致,分区定义明确(例如每月一个分区,P202401到P202412,再加一个P_MAXVALUE收容未来数据) - 用
INSERT INTO t1_new SELECT * FROM t1 WHERE created_at 分批插入(每次 10 万行,配合 <code>SLEEP(0.1)减轻主从延迟) - 同步期间用触发器或应用双写保证新数据不丢失(或停写一小段时间)
- 最后用
RENAME TABLE t1 TO t1_old, t1_new TO t1原子切换,再删旧表
注意:若原表有外键,必须先删外键约束,因为分区表不支持外键。
分区数量太多或太少分别有什么坑?
分区数不是越多越好。MySQL 单表最多 8192 个分区,但实际超过 100 个就容易出问题:
-
EXPLAIN输出变慢,优化器分析成本陡增 -
SHOW CREATE TABLE结果巨大,某些客户端(如老版本 Navicat)会卡死 -
OPTIMIZE TABLE变成逐个分区执行,耗时翻倍
太少也不行:比如只分 4 个大分区(按年),那单个分区仍含几千万行,起不到“缩小扫描范围”的作用。经验法则是:单分区行数控制在 500 万以内,且确保最近 3–6 个月的数据能落在独立分区里,方便后续 DROP PARTITION 快速归档。
- 每个分区应有明确命名(如
P202406),别用默认名p0、p1——后期运维无法直观判断数据归属 - 新增分区要用
ALTER TABLE t1 REORGANIZE PARTITION p_maxvalue INTO (PARTITION P202407 VALUES LESS THAN (TO_DAYS('2024-08-01')), PARTITION p_maxvalue VALUES LESS THAN MAXVALUE),不能直接ADD PARTITION
为什么 ALTER TABLE 加分区后查询没变快?
常见原因不是分区本身失效,而是查询没命中分区裁剪:
-
WHERE created_at = '2024-06-15 14:22:03'能裁剪,但WHERE DATE(created_at) = '2024-06-15'不能——函数包裹分区键会导致全分区扫描 - 使用了
OR条件,如WHERE created_at ,优化器通常放弃裁剪 - 分区键类型和查询值类型不一致,比如分区键是
INT(存20240601),但查询写了WHERE created_at = '2024-06-01',隐式转换让索引失效
验证是否生效,看 EXPLAIN PARTITIONS 输出里的 partitions 列:如果显示 p202406,p202407 就对了;如果显示 p0,p1,...,p11 或全部分区名,说明没裁剪。
分区不是银弹,它解决的是“数据冷热分离”和“批量删除”的问题,而不是替代索引。该建的联合索引,一个都不能少。











