加唯一索引卡在Waiting for table metadata lock,根本原因是DDL需MDL写锁,却被未提交事务(含只读SELECT)持有的MDL读锁阻塞;须通过performance_schema.metadata_locks定位LOCK_STATUS='GRANTED'的持锁线程,结合INNODB_TRX确认trx_state='RUNNING'且trx_started超时的悬挂事务,优先KILL而非KILL QUERY以彻底释放锁。

加唯一索引时卡在Waiting for table metadata lock
这不是索引本身慢,而是DDL被阻塞了——MySQL在执行ALTER TABLE ... ADD UNIQUE INDEX前,必须获取该表的元数据写锁(MDL write lock),而这个锁会被任何未提交的事务(哪怕只是一条SELECT)持有的MDL读锁(MDL read lock)挡住。
关键点在于:InnoDB对“只读事务”也严格持锁。只要事务没COMMIT或ROLLBACK,它就一直持有这张表的MDL读锁;DDL需要的是排他的写锁,两者互斥,于是所有后续DDL都卡在Waiting for table metadata lock状态。
- 别信
SHOW PROCESSLIST里状态是Sleep就安全——这种连接可能已空闲两小时,但事务仍活跃 -
trx_query为空、trx_state = 'RUNNING'、trx_started远早于当前时间(比如超120秒),才是真凶信号 - 唯一索引比普通索引更敏感:它要全程校验重复值,过程更长,等待窗口更大,更容易暴露底层阻塞
KILL QUERY无效,必须KILL thread_id
用KILL QUERY <thread_id></thread_id>只终止当前语句,事务上下文还在,MDL读锁不释放,DDL继续卡着。真正起效的是KILL <thread_id></thread_id>——它强制回滚整个事务,释放MDL读锁和所有行锁。
注意两个陷阱:
- 别对状态是
altering table的线程直接KILL——它只是在等锁,杀掉后重试照样卡住 - 优先查
information_schema.INNODB_TRX确认目标线程是否确实无业务关联,再操作
为什么加唯一索引比普通索引更容易卡死
根本原因不是“唯一性检查更耗时”,而是它的执行路径更依赖表级一致性保障:
- 普通索引(非唯一)允许
ALGORITHM=INPLACE, LOCK=NONE在大多数场景下免锁;但唯一索引在校验阶段必须保证“全表无重复”,MySQL 5.7+虽支持在线添加,但仍需短暂的LOCK=SHARED或LOCK=DEFAULT阶段 - 若表上有长事务,这个短暂的共享锁升级为写锁的过程就会被阻塞,且无法跳过
- 如果唯一索引列上已有大量重复数据,MySQL还会在DDL中途报错并回滚,但锁释放时机不可控,加剧等待链
线上加唯一索引前必须做的三件事
预防永远比救火快:
- 执行
SELECT * FROM information_schema.INNODB_TRX WHERE TIMESTAMPDIFF(SECOND, trx_started, NOW()) > 60,清掉所有超60秒未提交事务 - 确认目标表没有正在运行的
SELECT ... FOR UPDATE、UPDATE或显式BEGIN未结束的客户端会话 - 加索引语句显式指定
ALGORITHM=INPLACE, LOCK=NONE(MySQL 5.6+),并提前在测试环境验证是否真能免锁——某些旧版本或特殊表结构下该组合仍会退化为拷表
最常被忽略的事实:一个没COMMIT的SELECT,和一个跑了两小时的UPDATE,对DDL的阻塞效果完全一样。锁协议不看SQL复杂度,只看事务是否关闭。


















