MySQL不支持对非分区表直接添加分区,必须通过CREATE TABLE+INSERT SELECT重建表。因为分区是建表时确定的物理结构,ALTER TABLE ... ADD PARTITION仅适用于已分区表,且主键必须包含分区键,否则报错。

不能直接用 ALTER TABLE ... ADD PARTITION 给已有非分区表加分区——MySQL 不支持这种操作。必须重建表。
为什么不能对现有表直接添加分区
MySQL 的分区是表创建时就确定的物理结构,ALTER TABLE ... ADD PARTITION 仅适用于**已经分区的表**,用于扩展分区范围(比如新增一个按月的 p202607)。对普通表执行该语句会报错:ERROR 1064 (42000): You have an error in your SQL syntax 或更明确的 ERROR 1503 (HY000): A PRIMARY KEY must include all columns in the table's partitioning function。
- 分区信息写在表定义(.frm + .ibd)里,非分区表没有分区元数据结构
- 引擎层(InnoDB)不提供“动态启用分区”的能力
- 试图绕过限制(如先
DROP PRIMARY KEY再加分区)会导致数据丢失或表损坏
正确做法:用 CREATE TABLE + INSERT SELECT 重建
本质是新建一个带分区的表,再把老数据导入。关键点不在“加”,而在“换”。
- 先用
SHOW CREATE TABLE old_table获取原表 DDL,复制并修改为分区版本(注意:主键必须包含分区键) - 新建分区表时,确保分区键字段(如
log_time)被包含在所有唯一索引中,例如:PRIMARY KEY (id, log_time) - 用
INSERT INTO new_table SELECT * FROM old_table迁移数据;大表务必分批(如加WHERE id BETWEEN ? AND ?)并关掉 autocommit - 原子切换:用
RENAME TABLE old_table TO old_table_bak, new_table TO old_table
示例片段:
CREATE TABLE logs_new (
id BIGINT AUTO_INCREMENT,
log_time DATETIME NOT NULL,
content TEXT,
PRIMARY KEY (id, log_time)
) PARTITION BY RANGE (TO_DAYS(log_time)) (
PARTITION p202501 VALUES LESS THAN (TO_DAYS('2025-02-01')),
PARTITION p202502 VALUES LESS THAN (TO_DAYS('2025-03-01')),
PARTITION pmax VALUES LESS THAN MAXVALUE
);迁移过程中的三个易踩坑点
重建不是简单复制粘贴,以下细节决定成败:
-
foreign_key_checks=0和unique_checks=0必须提前设置,否则大批量INSERT会极慢 - 目标表的
ROW_FORMAT、CHARSET、COLLATE必须和原表一致,否则隐式转换可能引发乱码或截断 - 如果原表有触发器、外键、全文索引,这些不会自动继承,需手动重建;尤其是外键,指向新表时要确认引用列类型完全一致
替代方案:考虑是否真需要分区
重建表有锁表风险,且对亿级表耗时可能达小时级。先验证分区是否真能解决问题:
- 用
EXPLAIN PARTITIONS看当前慢查询是否能命中分区剪枝(partitions列只显示用到的分区) - 检查 WHERE 条件是否总带分区键(如总是查
WHERE log_time BETWEEN ...),否则分区无效甚至更慢 - 对比
ALTER TABLE ... ALGORITHM=INPLACE加索引、或升级到 MySQL 8.0+ 并启用并行查询,有时比分区更轻量
真正需要分区的,往往是时间序列类大表且查询强依赖时间范围;其他场景,先优化索引或引入缓存,可能比重建更稳妥。


















