真正引发Waiting for table metadata lock的是事务中已持有的MDL共享锁与DDL所需的MDL排他锁冲突;SELECT后未提交的事务会持续持有MDL_SHARED_READ锁,阻塞需MDL_EXCLUSIVE的ALTER TABLE,且ALGORITHM=INPLACE或LOCK=NONE均无法绕过该锁等待。

开启事务后执行DDL本身不会直接导致等待;真正引发 Waiting for table metadata lock 的,是事务中**已持有的 MDL 共享锁**与 DDL 所需的 **MDL 排他锁(EXCLUSIVE)冲突**。
为什么 SELECT 之后的事务会卡住 ALTER TABLE
MySQL 在 Server 层自动加 MDL 锁,跟存储引擎无关。只要连接未设 autocommit=1,执行第一条 SELECT 就隐式开启事务,并立即持有该表的 MDL_SHARED_READ 锁——这个锁不会在语句结束时释放,而要等到:
-
COMMIT或ROLLBACK显式结束事务 - 连接断开(比如脚本退出但没调
conn.close()) - 超时断连(依赖
wait_timeout,默认 8 小时,远不够用)
此时另一个会话执行 ALTER TABLE,需要获取 MDL_EXCLUSIVE 锁,但与已持有的 SHARED_READ 不兼容,就会卡住。
如何快速定位谁在持锁而不是等锁
SHOW PROCESSLIST 和 INNODB_TRX 都不可靠:前者只反映当前动作,后者只管 InnoDB 事务。MDL 是 Server 层锁,必须查 performance_schema.metadata_locks:
MySQL 数据库管理技能,能够通过自然语言查询和管理数据库,支持 SELECT/INSERT/UPDATE/DELETE、表管理、备份恢复等操作。当用户提及 MySQL、数据库查询、建表或数据备份时使用此技能。
- 先确认开关已启用:
SELECT * FROM performance_schema.setup_instruments WHERE NAME = 'wait/lock/metadata/sql/mdl',确保ENABLED和TIMED均为YES - 再查具体表的锁归属(替换
your_db和your_table):SELECT OBJECT_SCHEMA, OBJECT_NAME, LOCK_TYPE, LOCK_STATUS, PROCESSLIST_ID, PROCESSLIST_USER, PROCESSLIST_HOST 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';
- 重点关注
LOCK_STATUS = 'GRANTED'且LOCK_TYPE为SHARED_READ或SHARED_WRITE——对应PROCESSLIST_ID就是真正在 hold 锁的连接
ALGORITHM=INPLACE 或 LOCK=NONE 能绕过 MDL 等待吗
不能。这两个参数只影响 DML 是否被阻塞、是否需要重建表,但不改变 DDL 对 MDL 排他锁的需求:
-
ALGORITHM=INPLACE:表示 DML 可并发执行,但 DDL 仍需先获取MDL_EXCLUSIVE锁,仍会等待已有共享锁释放 -
LOCK=NONE:表示不阻塞 DML,但依然要等MDL_EXCLUSIVE锁可用,不是“不等锁”
换句话说,DDL 操作永远绕不开 MDL 锁升级这一步,哪怕它本身是 online 的。
最易被忽略的一点:持锁者可能根本没在运行任何显眼语句——一个忘了 COMMIT 的交互式 MySQL 客户端、一个异常退出却未关闭连接的 Python 脚本、甚至一个刚查询完 information_schema 就静默挂起的监控任务,都可能是那个安静攥着 SHARED_READ 锁不放的源头。










