根本原因是元数据锁(MDL)被其他连接持有,而非索引创建慢;需通过performance_schema.metadata_locks查LOCK_STATUS='GRANTED'的持锁线程,联合INNODB_TRX与PROCESSLIST定位悬挂事务,再按KILL QUERY→KILL顺序释放锁。

创建索引卡在 Waiting for table metadata lock,根本不是“索引太慢”,而是有其他连接正拿着元数据锁(MDL)不放——哪怕它只是执行了一条没提交的 SELECT,也会阻塞所有 DDL。
查谁在持锁:必须用 performance_schema.metadata_locks
SHOW PROCESSLIST 看不到真凶,它只显示“正在等”的线程,不显示“安静持锁”的线程。MySQL 5.7+ 必须靠 performance_schema:
- 先确认采集已启用:
SELECT * FROM performance_schema.setup_instruments WHERE NAME = 'wait/lock/metadata/sql/mdl';,确保ENABLED和TIMED都是YES - 再查具体持锁者:
SELECT OBJECT_SCHEMA, OBJECT_NAME, LOCK_TYPE, LOCK_STATUS, PROCESSLIST_ID, PROCESSLIST_USER, PROCESSLIST_HOST FROM performance_schema.metadata_locks m JOIN performance_schema.threads t ON m.OWNER_THREAD_ID = t.THREAD_ID WHERE OBJECT_SCHEMA = 'your_db' AND OBJECT_NAME = 'your_table'; - 重点盯
LOCK_STATUS = 'GRANTED'的行,尤其是LOCK_TYPE为SHARED_READ或SHARED_WRITE的连接;如果对应线程的PROCESSLIST_COMMAND = 'Sleep'且PROCESSLIST_TIME > 300,基本就是悬挂事务
定位“Sleep 却 RUNNING”的悬挂事务
单看 INNODB_TRX 可能漏掉已断开但事务未清理的连接;单看 PROCESSLIST 又容易把空闲连接当真凶。必须联合查:
SELECT t.trx_id, t.trx_started, t.trx_state, p.ID, p.USER, p.HOST, p.DB, p.COMMAND, p.TIME, p.STATE, p.INFO FROM information_schema.INNODB_TRX t JOIN information_schema.PROCESSLIST p ON t.trx_mysql_thread_id = p.ID WHERE p.COMMAND = 'Sleep' AND t.trx_state = 'RUNNING' ORDER BY t.trx_started;-
trx_started时间越早,挂得越久,优先处理;trx_isolation_level = 'REPEATABLE READ'时,哪怕只执行过一条SELECT,从BEGIN就持有了MDL_SHARED_READ,会阻塞所有 DDL - 注意:
PROCESSLIST_INFO为空 +TIME很大,是连接池归还连接但没COMMIT的典型特征
KILL 的顺序不能错:先 KILL QUERY,再 KILL
直接 KILL 连接可能中断正在写 binlog 的事务,引发主从不一致。稳妥做法是分两步:
- 对持锁线程先执行:
KILL QUERY <PROCESSLIST_ID>;(终止当前语句,保留连接) - 如果仍不释放,再执行:
KILL <PROCESSLIST_ID>;(断开连接,强制回滚事务) - 切记:不要
KILL正在执行ALTER TABLE的那个“等待中”的线程——它没锁,杀它没用,反而可能让下一个排队的线程顶上来继续等
最易被忽略的一点:ALTER TABLE 类操作本身不会主动释放 MDL 锁,它只会在获取到锁后才真正开始建索引。所以问题永远不在“建索引慢”,而在“锁拿不到”。排查时眼睛要从 Waiting 转向 GRANTED,从表象沉到事务状态。


















