编程 ALTER TABLE 卡在 Waiting for table metadata lock:改表前的三处预查与执行参数

2026-09-23 21:31:58

ALTER TABLE 卡在 Waiting for table metadata lock:改表前的三处预查与执行参数

在线 DDL 最容易坑人的地方,是名字里有"在线"两个字。看到 ALGORITHM=INSTANTLOCK=NONE,很容易以为生产表加字段可以随手敲。真到业务高峰,一个长事务挂着,ALTER TABLE 等 MDL,后面的查询跟着排队,才发现事故不是出在改表本身,而是出在改表前没做判断。

以下按线上变更的视角走一遍:一张订单大表加字段,怎么判断能不能走 INSTANT,怎么提前发现 MDL 风险,怎么执行,怎么观察复制延迟,最后怎么复查。适用范围以 MySQL 8.x / InnoDB 为主,具体操作仍要按线上小版本和表结构再确认。

MDL 等待与队列效应

很多事故不是 DDL 执行慢,而是 DDL 在等 metadata lock。MySQL 为了保护表定义一致性,DDL 在开始和提交表定义时需要元数据锁。在线 DDL 的排他锁时间通常很短,但如果前面有长事务一直占着表的元数据锁,这个"很短"就会变成"等不到"。

更麻烦的是,等待中的 DDL 往往会形成队列效应。比如一个报表连接开了事务读 orders 没提交,ALTER TABLE 开始等排他 MDL,后面新的订单查询又被这个 pending 的 DDL 卡住。线上表现可能是:连接数升高、SQL 变慢、接口超时,但慢查询里你只看到一堆普通 SELECT。

第一步:先看有没有长事务

尤其是报表、后台导出、手工查询、定时任务,它们经常是 MDL 事故的源头。

SELECT trx_id, trx_state, trx_started,
       TIMESTAMPDIFF(SECOND, trx_started, NOW()) AS duration_sec,
       trx_mysql_thread_id, trx_query, trx_tables_locked, trx_rows_locked
FROM information_schema.innodb_trx
ORDER BY trx_started ASC;

第二步:看 metadata lock

生产上在变更前就准备好这条 SQL,出现 pending 能第一时间定位是哪张表、哪个线程:

SELECT OBJECT_SCHEMA, OBJECT_NAME, LOCK_TYPE, LOCK_STATUS, PROCESSLIST_ID
FROM performance_schema.metadata_locks ml
JOIN performance_schema.threads t ON t.THREAD_ID = ml.OWNER_THREAD_ID
WHERE OBJECT_SCHEMA = 'appdb' AND OBJECT_NAME = 'orders';

第三步:看 instant row version

MySQL 8.4 里,INSTANT 加列/删列会产生 row version,INFORMATION_SCHEMA.INNODB_TABLES.TOTAL_ROW_VERSIONS 可以看到累计值;MySQL 8.4 的上限是 64。这个值太高时,继续 INSTANT 可能直接报错,别等发布窗口里才发现。

SELECT NAME, TOTAL_ROW_VERSIONS FROM INFORMATION_SCHEMA.INNODB_TABLES WHERE NAME = 'appdb/orders';

执行:让 DDL 快速失败而不是无限等锁

真正上线时,不要让 DDL 无限等锁。可以在执行会话里设置一个比较短的 lock_wait_timeout,拿不到 MDL 就快速失败,先处理阻塞源,而不是把业务流量拖进来一起等。

SET SESSION lock_wait_timeout = 60;
-- 显式声明算法与并发,避免线上悄悄走重路径
ALTER TABLE appdb.orders
  ADD COLUMN remark VARCHAR(255) NOT NULL DEFAULT '',
  ALGORITHM=INSTANT, LOCK=NONE;

MDL 超时和行锁超时不是一套参数

注意 MDL 的等待超时和行锁不是一套参数:

  • ALTER 等 MDL 看 lock_wait_timeout,默认 31536000 秒(一年);
  • UPDATE/DELETE 改同一行看 innodb_lock_wait_timeout,默认 50 秒。

行锁等 50 秒会自己报错退出,MDL 默认却是一年,所以一条 ALTER 可能安安静静站队很久,既不超时也不报错。

从库也要盯

很多团队只盯主库执行成功,忽略从库。DDL 会写入 binlog 并在复制链路上执行,如果从库机器规格差、SQL 线程被别的任务拖住,读流量可能先在从库上慢下来。变更期间至少观察 Seconds_Behind_Source、复制错误、从库 CPU/I/O,以及业务读延迟。

变更前清单

  • 确认 MySQL 版本、表引擎、行格式、字段位置和目标 DDL 是否支持预期算法。
  • 确认 ALGORITHMLOCK 显式写在 SQL 里,避免线上悄悄走重路径。
  • 检查长事务、metadata locks、业务定时任务和手工查询窗口。
  • 确认备份可用,并准备回滚方案。新增字段通常让旧代码忽略即可,删字段则要更谨慎。
  • 设置短 lock_wait_timeout,拿不到锁先失败,不把业务拖进等待队列。
  • 执行期间观察连接数、QPS、错误率、主从延迟、慢查询和 MDL pending。
  • 执行后验证表结构、row version、索引生效情况和关键接口链路。

一个典型事故

一个后台导出开事务读大表,没人注意;DBA 执行了一个看起来很轻的加字段;DDL 在等 MDL,后续业务 SELECT 又排在 DDL 后面。最后大家盯着接口超时找代码问题,其实根因是一条没提交的查询。

小结

DDL 不要只问"这个操作是不是在线的",要问"它会等谁"。MySQL 8.x 的在线 DDL 确实比早年好用很多,尤其是 INSTANT 让不少表结构变更变得非常轻。但轻不代表无风险,ALTER TABLE 仍然是一次生产发布。建议:小版本核实、算法显式、MDL 预查、短等待失败、复制观察、事后复查。

复制全文 生成海报 MySQL InnoDB DDL 元数据锁 运维

推荐文章

程序员茄子在线接单