ALTER TABLE阻塞是因为需获取MDL写锁,而未提交事务持有的MDL读锁与其互斥;即使ALGORITHM=INPLACE或LOCK=NONE,元数据变更阶段仍需短暂X锁,故仍会排队等待。

因为 ALTER TABLE 默认要拿 MDL_WRITE 锁,而这个锁和所有 DML(SELECT/INSERT/UPDATE/DELETE)需要的 MDL_READ 锁互斥——不是它“慢”,是它一执行,别的语句全得排队等。
为什么 Waiting for table metadata lock 会雪崩式堆积
这个状态不是 DDL 自己卡住了,而是它被前面一个没结束的事务拦下了。只要有一个活跃的 SELECT 或 INSERT 没提交(哪怕只是 ORM 自动开启又忘了 COMMIT),就会一直持有 MDL_READ 锁;ALTER TABLE 拿不到 MDL_WRITE,后面所有新来的查询、写入也全被堵在同一个锁队列里。
常见诱因包括:
- 监控脚本执行了没加
LIMIT的全表SELECT - 应用层用了长事务但没显式关闭
- 连接池未正确回收游标,导致隐式事务持续挂起
查法:SELECT * FROM performance_schema.metadata_locks WHERE OBJECT_NAME = 'your_table' AND LOCK_STATUS = 'PENDING';,再关联 threads 表找源头线程。
ALGORITHM=INPLACE, LOCK=NONE 为什么还是阻塞
这两个参数只管“数据重排阶段”是否锁表,不管“元数据变更阶段”。DDL 开始时仍需短暂获取 MDL_WRITE 锁来更新数据字典、写 binlog——哪怕只持续几毫秒,只要此时有未释放的 MDL_READ,它就得等。
也就是说:
-
LOCK=NONE≠ 零等待:高并发下 DML 会排队抢 MDL -
ALGORITHM=INPLACE≠ 全程无锁:页分裂、索引重建等操作仍需短时锁 - 加
NOT NULL字段却不带DEFAULT,MySQL 会直接降级为ALGORITHM=COPY,全程锁表
大表上 ALTER TABLE 实际走的是哪种算法
MySQL 不会告诉你它悄悄切到了 COPY,而是静默执行——直到你看到 Waiting for table metadata lock 持续几分钟,才发现它其实在全表拷数据。
触发 COPY 的典型条件:
- 表含全文索引(MySQL 5.6 中必走 COPY;5.7+ 需显式指定
ALGORITHM=INPLACE才可能绕过) - 存在外键约束(5.6–5.7.5 不支持 INPLACE 添加字段)
- 有活跃的隐式长事务(
information_schema.innodb_trx中trx_started超 60 秒) - 字符集或排序规则不一致(比如字段是
utf8mb4_general_ci,但ALTER语句里隐式用了utf8mb4_0900_as_cs)
验证方法:SHOW CREATE TABLE your_table; 看当前定义,再比对你要执行的语句中字段类型、字符集、约束是否完全兼容。
主从延迟会让 DDL 故障更隐蔽
主库 ALTER TABLE 是原子动作,瞬间完成;但从库 SQL 线程单线程回放,大表变更可能卡住数小时。结果就是:
- 读写分离架构下,从库查询返回旧结构 → 报
Unknown column或类型转换失败 - ETL 任务抽不到新加字段,BI 报表断更
- 微服务缓存了旧表结构(如 MyBatis
ResultMap),重启前一直报错 - 延迟期间无法主从切换,故障恢复窗口被锁死
这不是“DDL 失败”,而是“DDL 成功了,但下游没跟上”——这种问题最难定位,因为它不报错,只默默错。
真正危险的不是语法写错,而是你以为加个字段只是改元数据,实际上 MySQL 正在后台逐行重写整张表,还顺手把所有业务请求按在地上摩擦。别信“支持 Online DDL”,要看清它是否真的在你的表、你的版本、你的上下文里生效。


















