根本原因是mysql 8.0暴露了旧版本中“装睡”的未提交悬挂事务:它们状态为sleep但trx_state='running',持有mdl锁不释放,导致ddl阻塞;需查innodb_trx中trx_query为空、trx_started早、trx_state='running'的记录定位。

MySQL 8.0升级后出现大量 Waiting for table metadata lock,根本原因不是锁机制变严了,而是旧版本中“装睡”的未提交事务在新版本下更暴露、更难绕过——它们持有MDL锁却不释放,卡死所有DDL和后续DML。
为什么升级后问题突然变多?
MySQL 8.0对事务状态跟踪更严格,且默认启用 performance_schema 和更多元数据采集器,让原本“隐形”的悬挂事务无处藏身。更重要的是:autocommit=0 下的一条 SELECT 在 5.7 可能因连接空闲超时被自动断开,而 8.0 的 wait_timeout 默认仍是 8 小时(28800 秒),导致这类事务挂着不动、持续持锁。
- Python/Java 应用用
pymysql或aiomysql连接,默认autocommit=False,执行完SELECT不commit()也不close(),锁就一直挂着 - DBA 或脚本手动
BEGIN后没COMMIT,去干别的事了,连接保持Sleep状态但trx_state='RUNNING' -
mysqldump --single-transaction进程未结束,它对所有涉及表都持MDL_SHARED_READ锁,直到 dump 完成
为什么SHOW PROCESSLIST找不到阻塞源?
因为真正持锁的线程,在 PROCESSLIST 里状态是 Sleep,INFO 为空,TIME 很大(比如 1200 秒),但它在 INNODB_TRX 中仍显示 trx_state = 'RUNNING'——这是典型的“事务已启动、语句已执行完、但没提交”的悬挂状态。
-
SHOW PROCESSLIST只反映当前正在执行的语句,不反映事务生命周期 - 必须查
information_schema.INNODB_TRX,按trx_started倒序,重点筛trx_query IS NULL且trx_state = 'RUNNING'的记录 - 再用
performance_schema.metadata_locks关联确认:该线程是否对目标表持有LOCK_TYPE = 'SHARED_READ'或'SHARED_WRITE'
如何快速定位并安全终止悬挂事务?
别一看到 TIME > 600 就 KILL,先交叉验证是否真在空转:
- 执行
SHOW ENGINE INNODB STATUS\G,在TRANSACTIONS部分找对应trx_id,看mysql tables in use和locked tables是否都为 0 - 查
performance_schema.threads和events_statements_current,确认SQL_TEXT为空或NULL - 优先执行
KILL QUERY <thread_id></thread_id>(对Sleep连接无效但安全),等 10–20 秒无变化再KILL <thread_id></thread_id>
预防比排查更重要
升级后不改配置,等于把老问题放大了十倍。关键动作必须做:
- 全局设置
wait_timeout = 300(5 分钟)和interactive_timeout = 300,避免连接长期空闲还持锁 - 应用层强制
autocommit=True(只读场景),或确保每个数据库操作块都包裹try/finally:显式commit()或rollback() - 连接池配置里加
max_idle_time=300,并启用连接有效性检测(如test_on_borrow) - DDL 操作前,例行执行
SELECT * FROM sys.schema_table_lock_waits\G,确认无阻塞链
最常被忽略的点:REPEATABLE READ 隔离级别下,事务从 BEGIN 开始就持有 MDL 锁,不是从第一次 SELECT 才开始——这意味着哪怕你只写了 BEGIN 就断开,锁也已经挂上了。











