该错误非数据本身重复,而是mysql 5.6+ online ddl机制在并发dml下缓存回放时触发主键校验失败所致;ddl执行中dml未实时校验,回放阶段才报错,故表中查不到重复id。

ALTER TABLE报错Duplicate entry for key 'PRIMARY',先别动表结构
这个错误不是数据本身有问题,而是MySQL 5.6+的Online DDL机制在并发场景下触发的校验延迟。DDL执行过程中,缓存的DML操作(比如INSERT)没做实时主键检查,等回放时才爆冲突——所以你查表里根本找不到重复ID,但ALTER还是失败。
常见诱因:ALTER TABLE ... ADD COLUMN、ADD INDEX这类操作在业务高峰期跑,尤其当表上有高频写入时极易中招。
- 立刻停止重试:反复执行同一
ALTER只会累积更多缓存DML,扩大失败概率 - 查当前活跃事务:
SELECT * FROM information_schema.INNODB_TRX WHERE TIME_TO_SEC(NOW()) - TIME_TO_SEC(TRX_STARTED) > 60;,杀掉长事务能降低风险 - 临时关闭Online DDL(仅限紧急修复):
SET SESSION alter_algorithm='INPLACE';换为COPY模式,但会锁表
用pt-online-schema-change绕过Online DDL陷阱
pt-online-schema-change不依赖MySQL原生Online DDL,它用触发器捕获变更、分块拷贝数据,全程不卡主业务写入,天然规避“缓存DML回放冲突”问题。
典型命令:
pt-online-schema-change \ --host=localhost \ --user=root \ --password=xxx \ --alter "ADD COLUMN status TINYINT DEFAULT 0" \ D=test,t=users \ --execute
注意点:
- 必须确保表有主键或唯一索引,否则工具无法安全分片
- 执行前用
--dry-run预检,看是否提示Cannot chunk the table - 如果表有外键,加
--alter-foreign-keys-method=auto自动处理
ALTER前手动清理潜在冲突值
即使不用pt工具,也要在ALTER前确认:表里没有“肉眼不可见”的重复主键隐患。重点查三类情况:
- 自增ID被人工干预过:
SHOW TABLE STATUS LIKE 'users';比对Auto_increment值和MAX(id),若前者 ≤ 后者,说明下次INSERT可能撞上已有ID - 空字符串或0被当有效值:
SELECT COUNT(*) FROM users WHERE id = 0;或WHERE email = '';,这类值在唯一索引里是严格判重的 - 复合唯一索引字段组合重复:
SELECT user_id, order_date, COUNT(*) FROM orders GROUP BY user_id, order_date HAVING COUNT(*) > 1;
修复示例:ALTER TABLE users AUTO_INCREMENT = (SELECT MAX(id) + 1 FROM users);(需确保无事务正在写入)
为什么ON DUPLICATE KEY UPDATE救不了ALTER TABLE?
因为ON DUPLICATE KEY UPDATE是INSERT语句的子句,而ALTER TABLE是DDL操作,两者不在同一层级。你在ALTER时报错,说明冲突发生在MySQL内部重建表结构阶段,不是你的SQL语句能控制的。
强行在应用层加重试逻辑(比如捕获1062错误后改用REPLACE)毫无意义——ALTER失败后表结构没变,重试只是重复触发同样的底层冲突。
真正要盯住的是:DDL执行窗口是否干净。线上大表变更,宁可选凌晨3点停写5分钟,也别赌Online DDL在高并发下的稳定性。











