MySQL 8.0升级后出现Waiting for table metadata lock,主因是未提交的长事务“装睡”:状态为Sleep但trx_state='RUNNING',持有MDL锁却不释放,导致DDL阻塞;需查INNODB_TRX而非PROCESSLIST定位,重点筛查trx_started早、trx_query为空、trx_isolation_level=REPEATABLE READ的悬挂事务。

MySQL 8.0升级后出现Waiting for table metadata lock,大概率不是锁变严了,而是未提交的长事务在“装睡”——它没报错、没超时、不释放MDL锁,却卡死所有DDL。
为什么SHOW PROCESSLIST里看不到阻塞源?
因为真正卡住DDL的事务,可能状态是Sleep,但trx_state = RUNNING。INNODB_TRX显示它还在运行,而PROCESSLIST只显示当前正在执行的语句(比如一条早已执行完的SELECT),所以你查SHOW FULL PROCESSLIST会看到一堆Sleep线程,却找不到谁在“持锁不放”。
- 必须查
information_schema.INNODB_TRX,按trx_started倒序,重点看trx_mysql_thread_id和trx_query为空但trx_state = 'RUNNING'的记录 - 这些线程往往对应应用层开启了事务但忘了
COMMIT或ROLLBACK,甚至只是SELECT ... FOR UPDATE后挂起 -
trx_started时间早于当前时间10分钟以上,基本可判定为悬挂事务
如何快速定位并确认悬挂事务?
单条SQL就能串起线索:先找可疑事务,再反查它最后执行的语句。
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
- 查长事务:
SELECT trx_id, trx_mysql_thread_id, trx_started, trx_state, trx_autocommit FROM information_schema.INNODB_TRX ORDER BY trx_started LIMIT 10; - 查它最后执行的SQL:
SELECT THREAD_ID, SQL_TEXT FROM performance_schema.events_statements_current WHERE THREAD_ID = ?;(把上一步的trx_mysql_thread_id填进去) - 如果
SQL_TEXT为空或为NULL,且trx_state = 'RUNNING',基本坐实是未提交的悬挂事务 - 注意:别只看
PROCESSLIST里的Command = Sleep——这恰恰是伪装最深的阻塞源
sys.innodb_lock_waits查不到等待,是不是就没锁问题?
不是。这个视图只反映行锁等待,而Metadata lock属于元数据锁(MDL),它不走InnoDB引擎层,也不进data_locks或innodb_lock_waits。查MDL锁得用performance_schema.metadata_locks。
- 查当前所有表级MDL状态:
SELECT OBJECT_SCHEMA, OBJECT_NAME, LOCK_TYPE, LOCK_DURATION, LOCK_STATUS, OWNER_THREAD_ID FROM performance_schema.metadata_locks WHERE OBJECT_TYPE = 'TABLE'; - 找“一个表上同时存在
GRANTED和PENDING”的组合,说明有线程在等这张表的MDL - 再关联
performance_schema.threads查出对应线程的PROCESSLIST_INFO,就能定位到具体SQL - 特别注意:监控脚本如果频繁查
sys.innodb_lock_waits,在8.0里反而会主动申请MDL锁,自己变成阻塞源
杀掉线程前,怎么判断能不能安全KILL?
不能只看trx_state,要结合事务是否持有关键资源、是否在做写操作、是否有外部依赖。
- 如果
trx_query为空、trx_operation_state是NULL、且trx_isolation_level是REPEATABLE READ,大概率只是空闲事务,可KILL - 如果
trx_weight > 1000(表示修改/锁定行数多),或者trx_rows_locked > 0,说明它确实改过数据,KILL前建议先确认业务影响 - 用
KILL <thread_id></thread_id>比KILL QUERY <thread_id></thread_id>更彻底,因为后者只中断当前语句,事务仍保持打开 - KILL后观察
SHOW ENGINE INNODB STATUS\G的TRANSACTIONS部分,确认该事务已消失
MDL锁问题最麻烦的地方在于:它不报错、不超时、不释放,只安静地堵死所有DDL。排查时必须跳出“找正在执行的SQL”这个惯性思维,转而盯住那些“看似休眠、实则霸占锁”的长事务——它们往往藏在INNODB_TRX里,而不是PROCESSLIST中。

















