真正卡住DDL的是持有S MDL锁的长事务,需查performance_schema.metadata_locks中LOCK_DURATION='TRANSACTION'且LOCK_STATUS='GRANTED'的记录,再结合INNODB_TRX与PROCESSLIST定位“Sleep却RUNNING”的悬挂事务,最后用KILL CONNECTION释放锁。

真正卡住DDL的不是那个显示Waiting for table metadata lock的线程,而是某个没提交的SELECT、UPDATE或隐式事务——比如应用执行完BEGIN就断开连接,或者Python连接池复用连接但没显式conn.close()。
查performance_schema.metadata_locks定位持锁者
SHOW PROCESSLIST看不到谁在持锁,它只反映执行状态。必须靠performance_schema.metadata_locks:
- 先确认采集已启用:
SELECT * FROM performance_schema.setup_instruments WHERE NAME = 'wait/lock/metadata/sql/mdl',确保ENABLED和TIMED都是YES - 查目标表的持锁记录:
SELECT OBJECT_SCHEMA, OBJECT_NAME, LOCK_TYPE, LOCK_DURATION, PROCESSLIST_ID 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' AND LOCK_STATUS = 'GRANTED' - 重点盯
LOCK_DURATION = 'TRANSACTION'的行——这种锁不会随语句结束释放,只随事务提交或回滚才松手 - 如果对应
PROCESSLIST_COMMAND = 'Sleep'且PROCESSLIST_TIME > 300,基本就是悬挂事务
联合INNODB_TRX和threads找“Sleep却RUNNING”的悬挂事务
单查INNODB_TRX可能漏掉已断开但事务未清理的连接;单看PROCESSLIST又容易误判空闲连接。必须JOIN关联查:
SELECT t.trx_id, t.trx_started, t.trx_state, t.trx_isolation_level, 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- 关键判断点:
TIME > 300且INFO为空 → 极大概率是应用异常中断或忘记COMMIT -
trx_isolation_level = 'REPEATABLE READ'→ 即使只执行过一次SELECT,事务一启动就持MDL_SHARED_READ锁 - 别只看
trx_mysql_thread_id,要结合p.HOST和p.USER定位到具体应用实例或DBA终端
该KILL QUERY还是KILL CONNECTION?
选错等于雪上加霜:
- 如果持锁者是长
SELECT或空闲连接(Command = 'Sleep'且Time > 60),用KILL CONNECTION——因为锁绑在连接生命周期上,语句早结束了但连接还挂着 - 如果持锁者正在执行大事务(比如
UPDATE改了几十万行但还没提交),优先用KILL QUERY:它只中断当前语句,事务仍存在但MDL锁立即释放;而KILL CONNECTION会触发回滚,回滚过程本身持续持有MDL_EXCLUSIVE锁,阻塞时间可能翻倍 -
KILL QUERY对MDL无效,它只中断当前语句,事务和锁还在;必须用KILL <code>pid终止整个连接才能释放S MDL - 绝不能
KILLsystem user的SQL线程本身,那是复制通道,杀掉会导致复制中断
用sys.schema_table_lock_waits快速定位阻塞链
MySQL 8.0默认启用sys库,schema_table_lock_waits是现成的阻塞关系图谱,比手拼表快得多:
- 直接运行:
SELECT * FROM sys.schema_table_lock_waits\G,重点看三列:BLOCKING_PID(对应线程ID,可直接KILL)、BLOCKING_SQL(如果为空,说明阻塞者是隐式事务)、WAITING_PID(被卡住的DDL) - 该视图依赖
performance_schema正常采集,若查不到结果,先确认是否有DDL正在执行(如ALTER TABLE),否则可能根本没触发MDL等待 -
BLOCKING_PID为NULL不代表没锁,很可能是隐式事务(比如BEGIN后只跑了一条SELECT就断开)在持锁
最危险的盲区是:以为INNODB_TRX里没事务就安全了。MDL锁的持有者可能根本不出现在TRX_QUERY里,它只是个Sleep连接,但trx_state = 'RUNNING'且trx_started时间极早——这种连接最容易被忽略,也最常导致线上DDL卡死。


















