登录
首页 >  数据库 >  MySQL

MySQL 在线 DDL 实战:ALTER TABLE 别把业务卡在 MDL 上

来源:MySQL 官方文档

时间:2026-06-02 15:03:23 323浏览 收藏

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

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

MySQL 在线 DDL 思维导图
思维导图:在线 DDL 不只是一个 ALTER 语句,它至少包含算法选择、MDL 检查、复制观察和回滚预案。

业务场景:给 orders 表加一个来源字段

假设订单库里有一张 orders 表,八千万行,白天每秒几百到几千次写入。产品要补一个 source 字段,用来区分小程序、H5、直播间和第三方渠道。需求看起来很简单:

ALTER TABLE orders
  ADD COLUMN source VARCHAR(32) NULL,
  ALGORITHM=INSTANT,
  LOCK=NONE;

这条 SQL 本身没什么花活。真正的问题是:这张表当前有没有长事务?这个字段操作在你当前 MySQL 版本和表定义下是否支持 INSTANT?表已经做过多少次 instant add/drop column?复制链路能不能扛住这次 DDL?如果这些问题没答清楚,我不建议直接点执行。

先讲清楚三个算法:INSTANT、INPLACE、COPY

INSTANT 可以理解成“尽量只改数据字典和元信息”,对支持的操作非常快,比如某些加列、删列、索引元数据调整。但它不是万能钥匙,字段位置、字段类型、行格式、全文索引、压缩表等条件都可能影响是否支持。

INPLACE 听起来像“不复制表”,但它仍可能重建聚簇索引或消耗大量临时空间、redo、undo 和 I/O。它通常比 COPY 对业务友好,但不能简单等同于无成本。

COPY 基本就是高风险信号:MySQL 需要创建临时表、拷贝数据、切换表定义。大表上如果被迫走 COPY,我一般会改方案,比如用 gh-ost/pt-online-schema-change,或者拆分变更窗口。

最容易忽略的点:Online DDL 也要 MDL

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

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

MySQL 在线 DDL 上线流程图
流程图:我会把在线 DDL 当成一次发布,而不是一次单独的 SQL 执行。

执行前检查:别等卡住了才去翻 processlist

第一步先看有没有长事务。尤其是报表、后台导出、手工查询、定时任务,它们经常是 MDL 事故的源头。

SELECT trx_id, trx_started, trx_mysql_thread_id, trx_query
FROM information_schema.innodb_trx
ORDER BY trx_started;

第二步看 metadata lock。生产上我更喜欢在变更前就准备好这条 SQL,出现 pending 能第一时间定位是哪张表、哪个线程。

SELECT OBJECT_TYPE, OBJECT_SCHEMA, OBJECT_NAME,
       LOCK_TYPE, LOCK_STATUS, OWNER_THREAD_ID
FROM performance_schema.metadata_locks
WHERE OBJECT_SCHEMA = 'shop'
  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 = 'shop/orders';

我的执行方式:让失败尽快暴露

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

SET SESSION lock_wait_timeout = 5;

ALTER TABLE orders
  ADD COLUMN source VARCHAR(32) NULL,
  ALGORITHM=INSTANT,
  LOCK=NONE;

这里显式写 ALGORITHM=INSTANTLOCK=NONE 的意义,是让 MySQL 帮你拒绝“悄悄降级”。如果当前操作不支持这个算法或锁级别,语句应该失败,而不是在你没意识到的情况下走一个更重的路径。

MySQL 在线 DDL SQL 和 MDL 检查案例
案例图:短 SQL 足够了,关键是变更 SQL、MDL 检查和风险结论要放在同一个发布单里。

复制延迟也要算进风险

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

如果你的服务读写分离比较重,我建议在发布单里明确:变更前从库延迟必须为 0 或处于可接受范围;变更中有人盯复制状态;变更后抽查主从表结构一致。不要等用户投诉“刚下单查不到来源字段”才发现从库还没追上。

上线清单:我会逐项打勾

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

我踩过的坑:真正危险的是“以为很快”

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

所以我的经验是:在线 DDL 不要只问“这个操作是不是 online”,要问“它在哪些阶段需要锁、拿不到锁时会怎么影响队列、失败后业务有没有损失”。这几个问题想清楚,很多事故在执行前就已经被挡住了。

总结

MySQL 8.x 的在线 DDL 确实比早年好用很多,尤其是 INSTANT 让不少表结构变更变得非常轻。但轻不代表无风险,ALTER TABLE 仍然是一次生产发布。我的建议是:小版本核实、算法显式、MDL 预查、短等待失败、复制观察、事后复查。做到这些,你才是真的在做在线 DDL,而不是赌这次运气不错。

声明:本文转载于:MySQL 官方文档 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>