不能真正“无锁”,但可几乎不阻塞业务写入:需显式指定ALGORITHM=INPLACE、LOCK=NONE,并彻底清理重复数据;卡住主因是MDL锁等待、全量唯一性校验及重复值直接报错中断。

不能真正“无锁”,但可以做到几乎不阻塞业务写入——前提是显式控制 ALGORITHM 和 LOCK,且数据已彻底去重。
为什么直接 ALTER TABLE ADD UNIQUE KEY 会卡住业务
不是索引本身要锁全表,而是三个隐性环节在拖后腿:
- Prepare/Commit 阶段必须获取元数据锁(MDL),若有长事务或慢查询占着表,
ALTER就一直卡在Waiting for table metadata lock - 唯一性校验需扫描全部主键记录 + 构建新索引,I/O 和 CPU 压力大,从库容易延迟
- 遇到重复值直接报错
ERROR 1062 (23000): Duplicate entry 'xxx' for key 'uk_col',不提示位置、不跳过、不继续
这个错误常被误读为“语法错”或“权限问题”,其实只是数据没清理干净。
查重和清理必须手动做,MySQL 不会帮你判断哪条该留
唯一索引允许多个 NULL,但业务上是否接受“空值算不同”得自己拍板。先确认语义,再动手:
- 查重复项:
SELECT email, COUNT(*) FROM users GROUP BY email HAVING COUNT(*) > 1; - 若要保留最大
id的那条:DELETE t1 FROM users t1 INNER JOIN users t2 WHERE t1.email = t2.email AND t1.id - 执行前务必在备份库或从库验证逻辑,
DELETE没WHERE条件就完了
别信“先建索引再修数据”的想法——MySQL 不支持跳过重复校验。
用 ALGORITHM=INPLACE, LOCK=NONE 启动 Online DDL
这是 MySQL 5.6+ 原生能力,不用第三方工具,但必须显式写全参数,否则默认退化为 COPY 模式(即锁表重建):
ALTER TABLE users ADD UNIQUE KEY uk_email (email), ALGORITHM=INPLACE, LOCK=NONE;
失败常见原因:
- 表含外键约束(需先删外键,加完再加回)
- 列类型不支持在线变更,比如
TEXT列参与唯一索引(可改用前缀索引email(191)) - 存在未提交事务长期持有 MDL 锁(用
SELECT * FROM information_schema.INNODB_TRX ORDER BY TRX_STARTED LIMIT 5;查) - 引擎不是 InnoDB(MyISAM 不支持
INPLACE)
注意:LOCK=NONE 仅保证 DML(INSERT/UPDATE/DELETE)不被阻塞,DDL 自身在 Prepare/Commit 阶段仍需极短 MDL,所以仍要避开大查询窗口。
最容易被忽略的点:NULL 值语义和从库同步压力
多个 NULL 在唯一索引中合法,但如果你的业务逻辑把空邮箱当作“未填写”,而另一处又用 IS NULL 做去重判断,就会漏掉真实重复;另外,Online DDL 的 Row Log 回放可能拖慢从库,尤其当主库写入密集时。建议加索引前先在从库 STOP SLAVE; START SLAVE; 触发一次快速追赶,再操作主库。


















