未提交的select事务会持有mdl读锁,导致alter table无法获取写锁而卡住;algorithm=inplace/instant仅优化数据变更阶段,仍需等待mdl写锁;定位需查performance_schema.metadata_locks并kill持锁事务。

因为未提交的查询会持有 MDL 读锁,而 ALTER TABLE 必须等待写锁,二者互斥——不是“慢”,是根本拿不到锁。
MDL 锁怎么被一条 SELECT 挂住?
只要执行了访问表的语句(哪怕只是 SELECT COUNT(*) FROM users),MySQL 就自动加 MDL_SHARED_READ 锁;如果事务没结束(autocommit=0 且没 COMMIT 或 ROLLBACK),这个锁就一直挂着。
- 常见静默场景:
SET autocommit=0后只查了一条数据就闲置连接、ORM 框架开启事务后异常退出未回滚、监控脚本用START TRANSACTION扫描后忘关 -
SHOW PROCESSLIST里看到状态是Sleep,但Time值很大(比如 >60),基本就是它 - 这种锁不显现在
INNODB_TRX的trx_state='RUNNING'里,得查performance_schema.metadata_locks和threads关联视图
为什么 ALGORITHM=INPLACE 还卡住?
ALGORITHM=INPLACE 和 LOCK=NONE 只控制数据页修改阶段是否锁表,但 DDL 开始前必须获取 MDL 写锁——这个瞬间仍需排他,遇到已有 S 锁就会等。
- 哪怕你加的是可空字段:
ALTER TABLE users ADD COLUMN note TEXT NULL,只要前面有未提交的SELECT,照样卡在Waiting for table metadata lock - MySQL 8.0 中
ALGORITHM=INSTANT也逃不开:它跳过拷表,但元数据变更仍需短暂 X 锁 - 真正能绕开的只有外部工具,比如
pt-online-schema-change,它不走原生 DDL 路径
怎么快速定位并干掉“钉子户”?
别等 lock_wait_timeout(默认一年),直接查谁在 hold 锁:
- 先确认
performance_schema已启用:SELECT * FROM performance_schema.setup_instruments WHERE NAME = 'wait/lock/metadata/sql/mdl',若ENABLED是NO,得先UPDATE ... SET ENABLED='YES' - 查持锁者:
SELECT OBJECT_SCHEMA, OBJECT_NAME, LOCK_TYPE, LOCK_DURATION, 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_NAME = 'your_table' AND LOCK_STATUS = 'GRANTED' - 重点盯
LOCK_DURATION = 'TRANSACTION'的记录——这种锁不会随语句结束,只随事务结束 - 拿到
PROCESSLIST_ID后,谨慎执行KILL [id];若不确定,先SELECT trx_started, trx_query FROM information_schema.INNODB_TRX WHERE trx_mysql_thread_id = [thread_id]确认
真正麻烦的从来不是 DDL 本身,而是那些没人记得自己开了事务的连接——它们安静地挂着 MDL 锁,像一根看不见的线,把整个表的结构变更和后续所有读写全拽停。











