先开启performance_schema的mdl采集器,再查metadata_locks定位阻塞链;重点排查autocommit=0、隐式事务及未提交的select ... for update,结合innodb_trx与data_locks识别“幽灵事务”并及时终止。

MySQL 8.0迁移后MDL锁暴增,先查performance_schema.metadata_locks
不是SQL慢了,是ALTER TABLE、CREATE INDEX或甚至SELECT(带FOR UPDATE)卡在Waiting for table metadata lock——这大概率是元数据锁(MDL)争用,而非InnoDB行锁。MySQL 8.0默认启用performance_schema,但metadata_locks表默认不采集,查出来为空不是没锁,是开关关着。
必须先开采集器:
UPDATE performance_schema.setup_consumers SET ENABLED = 'YES' WHERE NAME = 'global_instrumentation';UPDATE performance_schema.setup_instruments SET ENABLED = 'YES', TIMED = 'YES' WHERE NAME = 'wait/lock/metadata/sql/mdl';
然后才能查到真实阻塞链:
SELECT OBJECT_SCHEMA, OBJECT_NAME, LOCK_TYPE, LOCK_DURATION, LOCK_STATUS, OWNER_THREAD_ID FROM performance_schema.metadata_locks WHERE LOCK_STATUS = 'PENDING';
注意:LOCK_DURATION = 'TRANSACTION'的锁会一直持到事务结束,哪怕只执行了一条SELECT;而STATEMENT级锁在语句结束就释放,更安全。
metadata_locks里看不到持有者?关联threads和events_statements_current
metadata_locks只告诉你“谁在等”,不直接告诉你“谁拿着”。要定位持有MDL的会话,得靠OWNER_THREAD_ID反查:
- 用
OWNER_THREAD_ID去performance_schema.threads查PROCESSLIST_ID和TYPE(是前台连接还是后台线程) - 再用该
THREAD_ID去performance_schema.events_statements_current查SQL_TEXT,看它卡在哪条语句上 - 特别注意
TYPE = 'FOREGROUND'且PROCESSLIST_STATE = 'Sleep'的线程——它可能开了事务但没提交,MDL一直挂着
一个典型陷阱:events_statements_current默认只存当前正在执行的语句,如果持有者已执行完语句但事务未提交,这里会是NULL。此时必须结合information_schema.INNODB_TRX查TRX_STARTED时间,再人工比对。
为什么迁移后MDL锁突然变多?重点盯autocommit=0和隐式事务
MySQL 8.0对MDL的加锁逻辑没变,但迁移常伴随应用配置变更:连接池升级、驱动更新、框架默认事务行为调整。最常见诱因是应用层误设autocommit=0,导致每个HTTP请求都开启一个长事务,哪怕只读也持MDL。
验证方式:
- 查
information_schema.INNODB_TRX里TRX_STATE = 'RUNNING'但TRX_ROWS_MODIFIED = 0的事务——纯读事务却不开自动提交 - 查
performance_schema.threads中PROCESSLIST_INFO IS NULL但PROCESSLIST_STATE = 'Sleep'的连接,它们极可能处于隐式事务中 - 检查应用是否在事务外执行了
SELECT ... FOR UPDATE——这种语句即使没显式BEGIN,也会触发隐式事务并持有MDL
这类问题在线上往往表现为:监控看到metadata_locks pending数缓慢爬升,但INNODB_TRX里没明显长事务——因为锁在threads层面,不在事务层面。
DDL操作卡住时,别只盯着metadata_locks,顺手看data_locks
迁移期间高频执行ALTER TABLE,它本身要拿S型MDL(共享),但若此时有长事务正持有X型MDL(排他),DDL就会堵住。这时候metadata_locks只能看到DDL在等,看不到那个“幽灵事务”到底在干啥。
真正关键的是:那个长事务可能同时在InnoDB里持着大量行锁或间隙锁,拖慢自身提交速度,间接延长MDL持有时间。所以必须同步查:
SELECT ENGINE_TRANSACTION_ID, OBJECT_SCHEMA, OBJECT_NAME, LOCK_MODE, LOCK_STATUS FROM performance_schema.data_locks WHERE LOCK_STATUS = 'GRANTED' AND ENGINE_TRANSACTION_ID IN ( SELECT trx_id FROM information_schema.INNODB_TRX WHERE TRX_STATE = 'RUNNING' );
如果发现某事务在data_locks里持着几百行X锁,但INNODB_TRX里TRX_QUERY却是NULL,基本可断定:它执行完DML但没COMMIT,MDL锁就这么挂着——这是迁移后最隐蔽也最致命的一类锁残留。











