MySQL 5.7大表加主键导致从库延迟飙升,是因为InnoDB必须物理重建聚簇索引,主库走COPY算法耗时长,从库只能串行重放完整DDL事件且无法并行化。

直接在大表上执行 ALTER TABLE ADD PRIMARY KEY 会触发全表重建,在主库耗时长、从库重放慢,极易引发严重延迟——这不是配置问题,而是 InnoDB 聚簇索引重建的物理本质决定的。
为什么大表加主键会让从库延迟飙升
MySQL 5.7 及以上版本中,InnoDB 表一旦添加主键,就必须按主键顺序物理重排所有数据页。这个过程:
- 主库上默认走
COPY算法(即使没显式指定),意味着新建临时表、逐行拷贝、重建二级索引、原子替换——对千万级以上表,常需数小时 - 从库只能串行重放该 DDL 的完整 binlog event(
Query_log_event或Table_map_event + Write_rows_event,取决于格式),无法并行化 - 若用
STATEMENT格式 binlog,从库还要重新执行全表扫描校验,进一步拖慢 - 更隐蔽的是:即使主库用了
ALGORITHM=INPLACE,只要表含外键、全文索引或分区,MySQL 内部仍可能降级为COPY,而你不会收到警告
必须先确认三件事,否则操作即失败
加主键前不验证,99% 的失败都卡在这三步:
- 查是否已有主键:
SHOW CREATE TABLE your_table,看输出里有没有PRIMARY KEY字段或约束名;有则必须先DROP PRIMARY KEY - 查目标列是否全非空:
SELECT COUNT(*) FROM your_table WHERE id IS NULL;哪怕只有一行NULL,ADD PRIMARY KEY就会中断并回滚 - 查目标列是否唯一:
SELECT id, COUNT(*) FROM your_table GROUP BY id HAVING COUNT(*) > 1;重复值必须业务定方案清理(删冗余、合并、生成新 ID)
生产环境推荐的分步操作流程
适用于 500 万行以上、不能停写、从库延迟敏感的场景:
- 第一步:加普通列并填充唯一序号(低峰期执行,行级锁不影响读)
ALTER TABLE your_table ADD COLUMN tmp_id BIGINT;UPDATE your_table t1 JOIN (SELECT id, ROW_NUMBER() OVER (ORDER BY created_at, id) AS rn FROM your_table) t2 ON t1.id = t2.id SET t1.tmp_id = t2.rn; - 第二步:删旧主键(如有)、改列并设约束(强制在线 DDL)
ALTER TABLE your_table DROP PRIMARY KEY, CHANGE tmp_id id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY FIRST, ALGORITHM=INPLACE, LOCK=NONE;
注意:LOCK=NONE在 MySQL 8.0+ 对主键变更才真正生效;5.7 只能用LOCK=SHARED,写操作会短暂阻塞 - 第三步:验证从库延迟与数据一致性
监控Seconds_Behind_Master是否回落;抽样比对主从SELECT MIN(id), MAX(id), COUNT(*) FROM your_table
最容易被忽略的兼容性陷阱
你以为加完主键就完了?这些点在跨版本迁移或混合部署时会突然爆发:
- MySQL 5.7 不支持
ROW_NUMBER(),必须改用变量模拟(但多线程下顺序不可靠),或提前升到 8.0+ -
ALGORITHM=INPLACE对主键变更的支持,和存储引擎强相关:只有 InnoDB 支持,MyISAM 直接报错 - 如果表上有外键引用,
DROP PRIMARY KEY会失败,必须先DROP FOREIGN KEY,且外键约束名得从SHOW CREATE TABLE里抄,不能猜 - 从库 binlog_format 必须是
ROW,否则LOCK=NONE下的 DDL 仍可能触发从库全表扫描校验


















