MySQL 的在线 DDL 主要减少表重建期间对读写的影响,但它并不代表整个 ALTER TABLE 都不需要等待锁。最容易被忽略的是元数据锁:如果目标表上有长事务,DDL 可能在开始或结束阶段等待独占元数据锁。实用的控制方式是,把算法和并发级别写进 DDL,再为执行 DDL 的当前会话设置一个合适的 lock_wait_timeout,超时后根据阻塞关系处理,而不是让迁移连接无限等待。
官方地址:https://dev.mysql.com/doc/refman/8.4/en/
先记住一个区分:lock_wait_timeout控制元数据锁等待,innodb_lock_wait_timeout控制 InnoDB 行锁等待。在线 DDL 的锁等待控制,通常先看前者;把后者调大或调小,不能直接解决长事务持有的表元数据锁问题。
一、先区分在线 DDL 的两类等待
在线 DDL 允许某些操作采用即时或原地算法,并在操作期间保留一定程度的并发 DML。但 DDL 仍要获取表级元数据锁来保证对象定义的一致性。一个已经打开目标表的长事务,即使没有持续读写,也可能让后续的 DDL 请求排队。
lock_wait_timeout 的单位是秒,作用域是 Global 和 Session,动态可调;MySQL 8.4 文档给出的默认值是 31536000 秒。它对每一次元数据锁尝试分别计时,一个语句如果需要获得多把元数据锁,整体阻塞时间可能超过单个变量值。
相对地,innodb_lock_wait_timeout 针对的是 InnoDB 记录锁等待,例如一个事务更新被另一个事务锁住的行。下面这张关系图只强调本文的对象:在线 DDL 申请元数据锁时,等待的是持有目标对象元数据锁的会话。
二、用 ALGORITHM 和 LOCK 声明边界
不要只写一条裸的 ALTER TABLE,然后把结果完全交给默认行为。MySQL 官方文档提供了 ALGORITHM 和 LOCK 子句,用于约束算法和并发访问级别。
ALGORITHM=INSTANT:要求使用即时变更;如果当前操作或表条件不支持该算法,应让语句失败,而不是悄悄退到更重的路径。ALGORITHM=INPLACE:要求使用原地算法;它不等同于“完全没有锁”,但通常可以在合适的操作中保留并发 DML。LOCK=NONE:要求允许读写并发;当前操作无法满足该并发级别时,DDL 会停止。LOCK=SHARED:允许读,但不承诺写入也能并发。- 即时操作只支持
LOCK=DEFAULT,不要把LOCK=NONE强行拼到所有ALGORITHM=INSTANT语句上。
例如,给订单表增加一个允许为空的字段时,可以把期望的算法明确写出来:
-- 只要求即时完成;不满足即时条件时让语句直接失败 ALTER TABLE orders ADD COLUMN source_channel VARCHAR(32) NULL, ALGORITHM=INSTANT;
如果变更类型需要原地执行,并且业务要求读写都保持可用,则可以写成:
-- 要求原地算法和读写并发;兼容性不满足时不要自动放宽条件 ALTER TABLE orders ADD INDEX idx_orders_created_at (created_at), ALGORITHM=INPLACE, LOCK=NONE;
这里的 LOCK=NONE 是并发访问要求,不是“等待 0 秒”。真正控制元数据锁排队时长的是下一节的会话变量。
三、只在迁移会话设置等待上限
正式执行 DDL 前,建议在专用迁移连接中设置 Session 级的 lock_wait_timeout。这样可以让本次迁移在遇到长事务时尽快返回,而不会直接改动全局配置,影响其他 DML、DDL 或管理连接。
-- 只影响当前迁移连接;5 秒内拿不到元数据锁就返回超时 SET SESSION lock_wait_timeout = 5; -- 先检查本连接的值,避免连接池复用旧会话配置 SELECT @@SESSION.lock_wait_timeout AS ddl_lock_wait_seconds; -- 例:要求原地算法和读写并发,锁等待由上面的 Session 值限制 ALTER TABLE orders ADD INDEX idx_orders_customer_status (customer_id, status), ALGORITHM=INPLACE, LOCK=NONE;
这个设置只限制等待元数据锁的时间,不限制 DDL 本身的构建时间。索引创建已经获得所需锁后,构建仍可能持续较长时间;不能用 lock_wait_timeout 推断整个 DDL 会在 5 秒内结束。
如果一次 DDL 需要访问多个对象,每一次元数据锁申请分别计时,所以“设置为 5 秒”也不应被理解为整个 SQL 的绝对 5 秒总预算。迁移脚本应把超时当成一个可识别的失败结果,记录目标表和 DDL 文本,再在阻塞者处理后重试。
四、用 metadata_locks 找到阻塞关系
只看到“DDL 卡住”还不够,需要判断是哪个会话持有目标表的元数据锁。Performance Schema 的 metadata_locks 表会展示已授予、等待中和超时中的元数据锁请求;把它和 threads 连接,可以获得连接标识和当前语句信息。
-- 同时查看目标表的持有者和等待者;PROCESSLIST_INFO 可能为空 SELECT ml.OBJECT_SCHEMA, ml.OBJECT_NAME, ml.LOCK_TYPE, ml.LOCK_DURATION, ml.LOCK_STATUS, t.PROCESSLIST_ID, t.PROCESSLIST_USER, t.PROCESSLIST_HOST, t.PROCESSLIST_INFO FROM performance_schema.metadata_locks AS ml LEFT JOIN performance_schema.threads AS t ON t.THREAD_ID = ml.OWNER_THREAD_ID WHERE ml.OBJECT_TYPE = 'TABLE' AND ml.OBJECT_SCHEMA = DATABASE() AND ml.OBJECT_NAME = 'orders';
重点看 LOCK_STATUS:GRANTED 表示已获得,PENDING 表示正在等待。一个常见的判断方式是:先找目标表上的 GRANTED 会话,再查看该会话是否处于长事务或空闲未提交状态;同时确认 DDL 自己的请求是否为 PENDING。
不要看到一个长时间运行的连接就直接终止它。先确认连接归属、事务是否仍有业务意义、是否已经进入提交阶段,并采用业务允许的方式提交、回滚或安排迁移窗口。本文只负责识别元数据锁依赖,不替读者决定是否可以杀掉连接。
五、按错误现象判断下一步
| 现象 | 更可能的原因 | 处理方向 |
|---|---|---|
| 等待一段时间后报元数据锁超时 | 目标表存在未释放的元数据锁 | 查看 metadata_locks,处理阻塞事务后重试 |
声明 LOCK=NONE 后立即停止 | 该变更不支持要求的并发级别 | 先确认业务能否接受更低并发级别,不要自动移除约束 |
声明的 ALGORITHM 不可用 | 操作类型或存储引擎不兼容该算法 | 查看官方支持矩阵,改为明确可接受的算法或调整变更设计 |
| 在线操作后段仍然等待独占锁 | 开始或结束阶段需要短暂独占元数据锁 | 缩短长事务、避开高峰,并保留合理的等待上限 |
| 运行很久后因在线日志过大失败 | 并发 DML 产生的临时在线日志超过限制 | 缩短迁移窗口、降低并发变更压力,再重新安排 DDL |
其中,“立即停止”和“等待超时”是两类不同结果。前者说明声明的算法或并发边界无法满足,后者说明语句在等待元数据锁时超过了当前会话的上限。不要用调大 lock_wait_timeout 来掩盖算法不兼容,也不要把 innodb_lock_wait_timeout 当成元数据锁排查开关。
六、一个可复用的迁移顺序
- 确认目标表为 InnoDB,并根据变更类型选择
ALGORITHM=INSTANT或ALGORITHM=INPLACE。 - 按业务要求声明
LOCK=NONE或LOCK=SHARED,让不满足要求的操作尽早失败。 - 在专用连接设置 Session 级
lock_wait_timeout,不要先改全局值。 - 执行前准备好查询
performance_schema.metadata_locks的语句,遇到等待时识别持有者。 - 把超时、算法不兼容、并发级别不满足和在线日志过大分别记录,修复对应原因后再重试。
这套顺序的核心是“先声明可接受的执行边界,再控制排队时间,最后根据锁关系处理阻塞”。在线 DDL 解决的是降低表不可用时间,不是消除元数据锁;迁移脚本仍然需要有超时、记录和重试策略。
常见问题
把 lock_wait_timeout 设置为 0,是否表示不等待?
不要用它表达“立即失败”的语义。MySQL 8.4 文档给出的允许范围从 1 秒开始;如果希望较快放弃,应设置一个明确的正数,并让迁移脚本处理超时结果。
LOCK=NONE 是否保证整个 DDL 都没有锁?
不是。它表示请求的并发级别允许读写继续进行;在线 DDL 的开始和结束阶段仍可能需要短暂的独占元数据锁,长事务仍可能造成等待。
为什么调小 innodb_lock_wait_timeout 没有解决 DDL 卡住?
因为它主要控制 InnoDB 行锁等待。DDL 卡在表定义的元数据锁上时,应检查 lock_wait_timeout 和 performance_schema.metadata_locks。
超时后是否可以直接重试?
只有在阻塞原因已经处理或迁移窗口已经改变时才重试。先确认阻塞会话是否仍然存在、DDL 的算法和并发声明是否适合目标表,并避免无条件快速重试形成新的锁竞争。
参考文档
https://dev.mysql.com/doc/refman/8.4/en/innodb-online-ddl.htmlhttps://dev.mysql.com/doc/refman/8.4/en/server-system-variables.htmlhttps://dev.mysql.com/doc/refman/8.4/en/performance-schema-metadata-locks-table.htmlhttps://dev.mysql.com/doc/refman/8.4/en/innodb-online-ddl-failure-conditions.html

MySQL optimizer_trace 观察成本估算分支
