定位源头需查performance_schema.metadata_locks中lock_status='granted'的持锁线程,结合threads和innodb_trx确认“sleep但running”的悬挂事务或隐式持锁会话,而非只看waiting线程。

ALTER TABLE 卡在 Waiting for table metadata lock 怎么定位源头
这不是DDL本身慢,而是它被前面一个没提交的会话拦住了。MDL锁和事务不完全绑定——SELECT、INSERT这类自动提交语句执行完就释放事务,但只要连接还开着、游标没关、ORM预热查询没结束,就可能持续持有共享MDL锁。
别只查 information_schema.INNODB_TRX,它看不到非事务型活跃会话。正确做法是:
- 查
performance_schema.threads,过滤TYPE = 'FOREGROUND'且PROCESSLIST_STATE IS NOT NULL的线程 - 连带查
performance_schema.metadata_locks,看OWNER_THREAD_ID对应哪个THREAD_ID,再反推PROCESSLIST_ID - 重点盯
LOCK_STATUS = 'PENDING'的请求,它的OWNER_THREAD_ID就是持锁者
ALGORITHM=INPLACE 和 LOCK=NONE 真的不加锁吗
不是不加锁,而是把排他锁(X锁)压缩到准备和提交两个瞬间。MySQL 5.6+ 的 Online DDL 本质是分阶段降级锁粒度,但元数据变更本身仍需排他MDL锁——否则无法安全替换表定义。
典型场景如 ALTER TABLE t ADD COLUMN c INT:
- 准备阶段:获取短暂
MDL_EXCLUSIVE锁,校验约束、分配空间,此时阻塞新DML进入 - 执行阶段:降为
MDL_SHARED_WRITE,允许并发SELECT/INSERT,但禁止其他DDL - 提交阶段:再次获取
MDL_EXCLUSIVE锁,原子替换.frm或数据字典项,完成后立即释放
所以 LOCK=NONE 只表示“不阻塞DML”,不代表“零锁等待”;一旦准备/提交阶段遇到长事务持S锁,照样卡住。
哪些操作会隐式持有MDL共享锁却不显眼
很多框架和中间件会在连接建立后自动执行“探活查询”,比如 SELECT 1 或 SELECT @@version,这些语句虽短,但在 autocommit=1 下仍会触发 MDL_SHARED_READ,且锁持续到语句结束——如果连接池复用连接、未及时关闭游标,这个锁就一直挂着。
常见隐形持锁点:
- Spring Boot 默认开启
spring.datasource.hikari.leak-detection-threshold=0,不报连接泄漏,但游标未 close 就等于 MDL 锁未释放 - Django ORM 的
connection.cursor()忘记.close(),或使用with connection.cursor() as c:但内部异常跳出导致未走 finally - MyBatis 的
<select></select>标签配了fetchSize="-2147483648"(即Integer.MIN_VALUE),某些驱动会因此保持游标打开
为什么 MyISAM 表 ALTER 一定全程阻塞
因为 MyISAM 不支持 Online DDL。它的 ALTER TABLE 必须走 COPY 算法:新建临时表 → 全量拷贝数据 → 替换原文件。整个过程需要全程持有表级 WRITE 锁 + MDL_EXCLUSIVE 锁,任何 SELECT 都会被挂起。
对比 InnoDB:
- InnoDB 支持
ALGORITHM=INPLACE的场景(如加普通字段、加二级索引),只在头尾抢锁 - MyISAM 没有行锁、没有 MVCC、没有 online 机制,
ALTER就是停服操作 - 哪怕只是
ALTER TABLE t ENGINE=InnoDB,也必须全表拷贝,同样全程锁表
真正容易被忽略的是:哪怕你只对一张小表做 ALTER,只要它用的是 MyISAM 引擎,就等于给整张表判了“死刑等待期”,期间所有读请求都会堆在 Waiting for table metadata lock 上,直到拷贝完成。











