MDL 是什么
元数据锁(Metadata Lock,简称 MDL)是 MySQL 5.5.3 引入的表级锁,用来保证语句执行期间表结构不被改动。任何查询和 DML 都会自动加 MDL 读锁,DDL 需要 MDL 写锁,两者互斥。因此一个没提交的事务就能把 ALTER TABLE 无限期堵住,而被堵的 DDL 又会把后面所有访问该表的语句排队堵住,形成雪崩式等待。
基本信息
| 适用版本 | MySQL 5.5.3 及以上,当前主线为 8.0 |
|---|---|
| 锁粒度 | 表级(OBJECT_TYPE 为 TABLE),另有全局与库级 |
| 释放时机 | 语句结束时(语句级)或事务提交/回滚时(事务级) |
| 查看入口 | performance_schema.metadata_locks(5.7.3 起)、sys.schema_table_lock_waits |
| 典型症状 | 进程状态显示 Waiting for table metadata lock |
典型使用场景
- ALTER TABLE 执行后长时间不返回,需要定位到底被哪个连接挡住
- 业务侧大量语句突然排队、连接数飙升,怀疑 DDL 与长事务互相阻塞
- 上线前巡检:确认没有未提交的事务会挡住即将执行的 DDL
- 排查应用关闭 autocommit 后遗留的空事务(只执行了 BEGIN 或一条 SELECT)
- 备份、导出或加索引工具持锁,导致后续 DDL 全部失败
定位 MDL 现场
定位 MDL 现场
-- 1) 看谁持有、谁在等(把 appdb 换成目标库)
SELECT OBJECT_SCHEMA, OBJECT_NAME, LOCK_TYPE, LOCK_STATUS, LOCK_DURATION
FROM performance_schema.metadata_locks
WHERE OBJECT_SCHEMA='appdb' ORDER BY OBJECT_NAME, LOCK_STATUS;
-- 2) 推荐:sys 视图一步给出阻塞链
SELECT waiting_pid, waiting_query, blocking_pid, blocking_query
FROM sys.schema_table_lock_waits;
-- 3) 找最老的未提交事务(最常见的持锁源头)
SELECT trx_id, trx_state, trx_started, trx_mysql_thread_id
FROM information_schema.INNODB_TRX ORDER BY trx_started LIMIT 5;阻塞链逐列读法
| 字段 | 含义 | 正常与异常 |
|---|---|---|
| LOCK_TYPE | SHARED_READ / SHARED_WRITE 是普通语句加的读锁 | EXCLUSIVE 是 DDL 申请的写锁,出现且 PENDING 就是要等了 |
| LOCK_STATUS | GRANTED 已持有,PENDING 在排队 | PENDING 的那一行通常就是看起来卡住的 ALTER |
| LOCK_DURATION | STATEMENT 语句级、TRANSACTION 事务级 | TRANSACTION 长时间不消失说明有事务没提交 |
| OBJECT_NAME | 被锁的表名 | 多张表同时 PENDING 说明有跨表 DDL 或批量操作 |
| THREAD_ID | 与 threads / PROCESSLIST 关联的线程号 | 拿到它才能确认源头并决定是否 KILL |
确认后清理源头
确认后清理源头
-- 用 sys 视图给出的 blocking_pid 找到对应连接
SELECT id, user, host, db, command, time, state, info
FROM information_schema.PROCESSLIST WHERE id = 12345;
-- 确认这个事务可以被牺牲后再执行
KILL 12345; -- 大事务的回滚可能比原语句更久
SHOW PROCESSLIST; -- 复查等待队列是否已散开MDL 等待很难靠超时兜底:lock_wait_timeout 默认 31536000 秒(约一年),实际等于无限等待。发现 DDL 被堵要主动定位处理,不要等它自己失败。
autocommit=1 时单条 SELECT 结束即释放 MDL,不会挡住 DDL;事故几乎都出在显式事务、关闭了自动提交的长连接,或事务里夹着慢查询。
优先处理 blocker 而不是 DDL:DDL 被中断后还要重来一遍,而等待期间业务已经受损;处理完源头再重新提交 DDL,并安排在低峰执行。