ALTER TABLE 对千万级大表改结构几乎必然锁表数分钟至数小时,因MySQL 5.7及更早版本默认锁表,8.0.12+仅对极少数操作真正支持无锁;卡在Waiting for table metadata lock是MDL阻塞所致,长事务或Sleep连接未释放表引用即导致阻塞;ALGORITHM=INPLACE和LOCK=NONE有严格前提,并非完全不锁;pt-online-schema-change是生产首选方案,绕开MDL锁,但要求主键或唯一非空索引且不能有现有触发器;验证是否真正在线执行需结合innodb_print_ddl_logs、INNODB_TRX状态及PROCESSLIST观察,而非仅看命令返回成功。

直接执行 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才能定位真正在跑的长事务 - 哪怕只执行过
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,会导致主从表结构不一致
如何验证某条 ALTER 是否真的在线执行
不能只看命令是否返回成功——要确认执行期间业务写入是否被阻塞。最可靠的方式是结合日志与状态监控:
- 开启
innodb_print_ddl_logs=ON(动态可设),执行后查错误日志,搜索DDL log关键字,看到add index类记录且无copy table即大概率是 inplace - 执行中查
SELECT * FROM information_schema.INNODB_TRX WHERE TRX_QUERY LIKE 'alter%',观察TRX_STATE是否长期卡在RUNNING(说明没卡住),同时检查TRX_MYSQL_THREAD_ID对应连接是否仍在处理业务请求 - 用
SHOW PROCESSLIST观察是否有大量Waiting for状态,特别是Waiting for table metadata lock
真正容易被忽略的是:即使你写了 ALGORITHM=INPLACE, LOCK=NONE,MySQL 仍可能因字段位置、默认值、字符集或存储引擎细节 silently 退回到 COPY 模式——它不会报错,也不会警告,只是悄悄锁表。所以线上操作前,必须用 EXPLAIN FORMAT=JSON 看执行计划,而不是依赖文档描述或版本号。


















