MDL锁是保护表元数据的“说明书锁”,非数据锁;读操作加共享MDL读锁,DDL加互斥MDL写锁;长事务未提交会阻塞DDL,需查INNODB_TRX定位并KILL连接释放锁。

MDL锁本质是表结构的“说明书锁”,不是数据锁
MDL(Metadata Lock)锁保护的是表的元数据,也就是字段定义、索引结构、主键信息这些“说明书”内容。只要执行任何访问表的操作(SELECT、UPDATE、INSERT、ALTER),MySQL 就会自动加 MDL 锁——你没法绕开,也不该手动干预。
关键区别在于:读操作加 MDL 读锁(多个可并存),DDL 加 MDL 写锁(互斥)。所以不是“谁在抢数据”,而是“谁在占着说明书不让改”。一个没提交的 SELECT 或 BEGIN 后空跑,就能让后续所有 ALTER TABLE 卡在 Waiting for table metadata lock 状态。
查阻塞源头别只看 SHOW PROCESSLIST
SHOW PROCESSLIST 显示的 Sleep 或长时间 Time 值,常常是假象。真正持有 MDL 锁的长事务,可能状态是 Query 但 Info 为空,或状态是 Sleep 且 Time 很大——这说明连接还活着,事务没提交,MDL 读锁仍在。
- 优先查
information_schema.INNODB_TRX,过滤条件:trx_state = 'RUNNING'且trx_started时间远早于当前(比如超 120 秒) - 特别关注
trx_query IS NULL的记录——大概率是BEGIN后没COMMIT,或是 Python/Java 连接池复用连接但未显式关闭 -
sys.schema_table_lock_waits(MySQL 8.0+)能直接给出BLOCKING_PID和BLOCKING_SQL,比手拼表快得多;若查不到结果,先确认是否有 DDL 正在执行,否则可能根本没触发等待
KILL QUERY 对 MDL 阻塞完全无效
KILL QUERY <code>pid 只中断当前语句,事务仍活跃,MDL 锁不释放。DDL 依然卡住。
必须用 KILL <code>pid 终止整个连接,才能触发回滚、释放 S-MDL 锁。
- 杀之前先查影响:
SELECT trx_id, trx_state, trx_isolation_level, trx_rows_modified FROM information_schema.innodb_trx WHERE trx_mysql_thread_id = ? - 别只看
trx_rows_modified = 0就杀——它可能刚执行完 SQL,正卡在应用层没提交,或正在做关键业务逻辑 - 如果阻塞者是报表导出、批量更新等合法长事务,优先联系业务方协调停机窗口,而非直接
KILL
预防比定位更关键:控制事务边界和 DDL 执行时机
MDL 阻塞不是性能问题,是事务生命周期与元数据变更的协议冲突。根治靠规范,不靠调参。
- 应用连接默认设
autocommit = 1,避免隐式事务长期持锁;所有显式BEGIN必须配对COMMIT或ROLLBACK - DDL 前强制检查:
SELECT * FROM information_schema.INNODB_TRX WHERE TIMESTAMPDIFF(SECOND, trx_started, NOW()) > 120,有就告警或暂停变更 -
lock_wait_timeout可设为 5 秒(SET GLOBAL lock_wait_timeout = 5),让卡住的 DDL 主动报错退出,避免连锁阻塞其他查询 - 从库 DDL 阻塞无法靠主库
ALGORITHM=INPLACE缓解,必须走pt-online-schema-change或手动跳过重放(需校验一致性)
最容易被忽略的一点:哪怕一条 SELECT * FROM t LIMIT 1 没提交,也会持续持有 MDL 读锁——DDL 不需要等它查完,只需要它释放“说明书”,而它不 COMMIT 就不放。


















