高并发下加主键特别危险,因mysql 5.7及更早版本默认用copy算法全表重建并阻塞dml,8.0+虽支持inplace但主键变更常降级为lock=shared,引发mdl锁堆积。

不能直接 ALTER TABLE ... ADD PRIMARY KEY,否则会触发全表 COPY、长时间锁表,线上读写可能卡住甚至超时失败。
为什么高并发下加主键特别危险?
MySQL 5.7 及更早版本对主键变更默认使用 COPY 算法:整张表复制重建,期间 DML(INSERT/UPDATE/DELETE)被阻塞,SELECT 也可能因元数据锁(MDL)等待而堆积。即使 MySQL 8.0+ 支持 ALGORITHM=INPLACE,但只要涉及主键变更,LOCK=NONE 并不总是生效——尤其当表含外键、全文索引、分区或存在未提交事务时,会被自动降级为 LOCK=SHARED 或更严。
- 现象:
ALTER TABLE t ADD PRIMARY KEY (id)执行数分钟无响应,SHOW PROCESSLIST显示大量Waiting for table metadata lock - 根本原因:主键即聚簇索引,变更意味着重排所有数据页,必须保证一致性快照
- 大表(千万行以上)在业务高峰执行,极易引发雪崩
安全加主键的三步分拆法(推荐 MySQL 8.0+)
把“加主键”这个原子操作拆成可观察、可中断、低影响的多个阶段,全程避免锁表。
- 第一步:加一个普通非空唯一列(不设主键,不自增)
ALTER TABLE t ADD COLUMN tmp_pk BIGINT NOT NULL UNIQUE AFTER id;
→ 使用ALGORITHM=INPLACE, LOCK=NONE(MySQL 8.0+ 默认支持) - 第二步:用
ROW_NUMBER()填充唯一值,确保顺序稳定UPDATE t JOIN (SELECT id, ROW_NUMBER() OVER (ORDER BY created_at, id) AS rn FROM t) r ON t.id = r.id SET t.tmp_pk = r.rn;
→ 行级锁,不影响其他查询;ORDER BY 必须明确,避免非确定性 - 第三步:删旧主键(如有)、切换新列为主键
ALTER TABLE t DROP PRIMARY KEY, CHANGE tmp_pk id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY FIRST;
→ 注意:若原表无主键,省略DROP PRIMARY KEY;若原主键是AUTO_INCREMENT,需先移除该属性再执行
大表或低版本 MySQL 的兜底方案:建新表 + 原子重命名
当无法确认 INPLACE 是否真正生效,或表结构复杂(含外键、触发器、FULLTEXT),这是唯一 100% 可控的方式。
- 新建带主键的表:
CREATE TABLE t_new LIKE t; ALTER TABLE t_new ADD COLUMN id BIGINT PRIMARY KEY AUTO_INCREMENT FIRST, MODIFY old_id BIGINT NOT NULL; - 逐批迁移数据(控制每批 1k–5k 行,避免长事务):
INSERT INTO t_new (id, ...) SELECT ROW_NUMBER() OVER (), ... FROM t ORDER BY old_id LIMIT 5000 OFFSET 0; - 校验一致性后,原子切换:
RENAME TABLE t TO t_old, t_new TO t; - 关键点:切换瞬间只锁元数据,毫秒级;迁移过程可随时暂停;务必提前在从库验证脚本
真正难的不是语法是否通过,而是你是否清楚这张表当前有没有活跃的长事务、下游 CDC 是否依赖 Binlog 位置、以及应用层是否缓存了旧主键逻辑——这些不会报错,但会让加完主键后系统行为悄然异常。











