直接执行alter table对千万级大表改结构几乎必然锁表数分钟至数小时,根本原因是mdl锁阻塞或io瓶颈;需通过show processlist查state状态定位卡点,再结合innodb_trx和performance_schema.metadata_locks精准识别持锁长事务。

直接执行 ALTER TABLE 对千万级以上大表改结构,几乎必然锁表数分钟到数小时——这不是配置问题,是 MySQL 5.7 及更早版本的默认行为,8.0.12+ 也只对极少数操作(如末尾加列)真正支持无锁。
为什么 ALTER TABLE 会卡在 Waiting for table metadata lock
这不是表数据被锁,而是元数据锁(MDL)阻塞。只要有一个长事务或 Sleep 连接没释放对目标表的引用,ALTER TABLE 就拿不到排他 MDL 锁,只能干等。
-
SHOW FULL PROCESSLIST中看到大量状态为Waiting for table metadata lock的线程,同时伴随一堆Command = Sleep、Time > 300、Info = NULL的连接,基本就是它们在 hold 住 MDL -
SELECT * FROM information_schema.INNODB_TRX WHERE TIME_TO_SEC(NOW() - trx_started) > 60才能定位真正在跑的长事务;SHOW OPEN TABLES WHERE In_use > 0对 MDL 无效,别依赖它 - 哪怕只执行过
SELECT * FROM t没提交,也会持有MDL_SHARED_READ,而ALTER TABLE需要MDL_EXCLUSIVE,二者互斥
ALGORITHM=INPLACE 和 LOCK=NONE 到底能不能用
能,但有严格前提,且不等于“完全不锁”。MySQL 会按规则判断是否可降级,失败就报错,不会静默退化。
-
ADD COLUMN在末尾添加且无NOT NULL DEFAULT值时,5.6+ 可走ALGORITHM=INPLACE;但若表非空又带NOT NULL DEFAULT,仍需全表回填 → 触发重建 -
MODIFY COLUMN类型变更(如VARCHAR(255) → VARCHAR(1024))在 5.7 中 100% 强制重建;8.0.12+ 仅当长度头不变、字符集不变、无存储格式升级时才可能 inplace - 加索引一般支持
LOCK=NONE,但唯一索引/全文索引/含 BLOB 字段时可能降级;执行前务必用EXPLAIN FORMAT=JSON确认"alter_algorithm": "inplace"和"supports_inplace": true
pt-online-schema-change 是生产环境首选方案
它绕开 MDL 锁本身:新建影子表、用触发器捕获原表变更、分块拷贝历史数据、最后原子切换表名。整个过程原表读写基本不受影响。
- 要求原表必须有主键或唯一非空索引,否则无法做增量同步;已有触发器的表不能用
- 执行前加
--dry-run --print看它生成的 SQL,尤其注意RENAME TABLE语句顺序和目标库名是否正确 - 禁止在从库单独运行再切主从——
pt-osc不复制 DDL,会导致主从表结构不一致 - 磁盘空间要预留 ≥ 原表大小 × 1.5,触发器写入和拷贝过程会产生额外日志与临时文件
业务层配合才是关键防线
工具再稳,也防不住应用侧连接泄漏或长事务。DDL 卡住后 KILL 掉 Sleep 连接只是救火,修复代码才是根治。
- 确认应用是否使用连接池且设置了合理的
maxLifetime和idleTimeout,避免连接空闲太久还挂着 MDL - 检查 ORM 或 DAO 层有没有开启事务后只查不提交(比如 Spring 的
@Transactional方法里漏了return或异常未抛出) - 线上 DDL 必须搭配监控:
performance_schema.metadata_locks表 +lock_wait_timeout告警(默认一年,建议调成 60–300 秒)
最易被忽略的是:MDL 锁等待超时由 lock_wait_timeout 控制,不是 innodb_lock_wait_timeout;前者默认 31536000 秒(一年),所以你看到的“卡住不动”,其实是它还在耐心等,而不是死锁或失败。











