DML操作本身不引发MDL锁表,而是持有SHARED_READ/SHARED_WRITE锁阻塞DDL;根本原因是未结束的事务长期持有MDL锁,尤其在REPEATABLE READ下,即使简单SELECT也会因事务未提交而持续持锁。

直接说结论:DML 操作本身不会“引发” Metadata Lock 锁表,而是它**持有**了 MDL 共享锁(SHARED_READ 或 SHARED_WRITE),从而**阻塞后续 DDL**。真正要处理的,不是 DML 语句本身,而是它背后那个没结束的事务。
为什么一条 SELECT 就能让 ALTER TABLE 卡住?
MySQL 在执行任何 DML(包括 SELECT)前,都会为涉及的表申请一个 MDL 读锁。这个锁的生命周期不取决于 SQL 执行时间,而取决于**事务生命周期**:
- 在
REPEATABLE READ隔离级别下,哪怕只执行一次SELECT,只要事务没提交或回滚,SHARED_READ锁就一直挂着 - 应用使用连接池时,常出现“连接归还了,但事务没 commit”的情况——
PROCESSLIST显示Command = 'Sleep',INNODB_TRX却显示trx_state = 'RUNNING' -
mysqldump --single-transaction也会为所有表加SHARED_READ,dump 没结束,锁就不放
怎么快速定位持锁的 DML 会话?
别只看 SHOW PROCESSLIST 里状态为 Waiting for table metadata lock 的线程——那是受害者。你要找的是安静持锁的“真凶”,用这两个查询组合:
先查谁在持锁(替换 your_db 和 your_table):
SELECT OBJECT_SCHEMA, OBJECT_NAME, LOCK_TYPE, LOCK_STATUS, 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'
AND LOCK_TYPE IN ('SHARED_READ', 'SHARED_WRITE');再确认该线程是否是悬挂事务:
SELECT t.trx_id, t.trx_started, t.trx_state, p.ID, p.USER, p.HOST, p.TIME, p.INFO FROM information_schema.INNODB_TRX t JOIN information_schema.PROCESSLIST p ON t.trx_mysql_thread_id = p.ID WHERE p.ID = ? -- 填上上一步查到的 PROCESSLIST_ID AND t.trx_state = 'RUNNING';
关键信号:TIME > 300 且 INFO 为空 → 极大概率是应用异常中断或忘记 commit。
杀错线程会导致更严重问题
常见错误是直接 KILL 那个卡在 Waiting for table metadata lock 的 DDL 线程。这毫无作用——锁还在别人手上。正确做法是:
- 优先执行
KILL QUERY <blocking_pid>(只终止当前语句,不杀连接),观察是否释放锁 - 若无效,再执行
KILL <blocking_pid>(杀整个连接),但要注意:如果该事务已修改大量数据,回滚可能耗时极长,甚至拖垮 IO - 严禁批量
KILL所有 Sleep 连接——很多是健康的应用连接池空闲连接
最稳妥的方式,是先用 SHOW ENGINE INNODB STATUS\G 搜索对应 trx_id,确认事务确实“空转中”、没有活跃操作再动手。
真正容易被忽略的一点:MDL 锁和事务隔离级别强绑定。READ COMMITTED 下,SELECT 不会提前持 MDL 锁;但线上绝大多数业务默认用 REPEATABLE READ,所以哪怕只是个简单查询,只要开了事务,就具备阻塞 DDL 的能力——这点在写定时任务或导入脚本时尤其危险。


















