MySQL 的 Online DDL 在全量拷贝阶段如何处理并发的 DML 增量数据
在 MySQL 中,Online DDL(在线数据定义语言)允许你在不阻塞 DML(数据操作语言,如 INSERT、UPDATE、DELETE)的情况下修改表结构。其中,全量拷贝阶段(COPY 算法)是耗时最长的步骤,它需要将旧表的数据逐行复制到新表,同时还要保证对原表的并发写入不被遗漏。MySQL 通过一种称为 Row Log(行日志)的机制来解决这个并发增量数据的捕获问题。
理解 Online DDL 的全流程
Online DDL 使用 COPY 算法时,大致分为三个阶段:
- 准备阶段:创建具有新结构的临时表、获取元数据锁、开启增量日志收集。
- 全量拷贝阶段:逐行将旧表数据复制到临时表,期间旧表仍可正常读写,所有 DML 操作会被记录到 Row Log。
- 应用阶段:停止写入旧表,应用 Row Log 中的增量数据到临时表,最后用临时表替换旧表。
其中,第二个阶段是处理并发 DML 的核心。下面我们拆解这个过程。
全量拷贝阶段:如何捕获并发 DML
1. 开启 Row Log 缓冲区
在全量拷贝开始前,Server 层会在内存中创建一个 Row Log 缓冲区。该缓冲区用于暂存拷贝过程中对原表产生的所有变更记录。缓冲区的大小由参数 innodb_online_alter_log_max_size 控制,默认值为 128MB。
- 操作:MySQL 在准备阶段自动分配该缓冲区,无需手动干预。
- 原理:缓冲区本质是一个环形队列,记录每条 DML 操作对应的主键值和变更类型(插入、删除、更新前后的镜像)。
2. 记录每一条 DML 变更
当全量拷贝正在进行时,其它连接对原表执行 DML 操作:
- INSERT:记录新行的主键值(或唯一键值),标记为“待插入”。
- DELETE:记录被删除行的主键值,标记为“待删除”。
- UPDATE:记录更新前后的主键值(若主键未变,只需记录新值),标记为“待更新”。
每条记录都带有 行号序列号(log sequence number),用于后续确定应用顺序。这些记录被追加到 Row Log 缓冲区中,同时在 ib_logfile 中持久化(确保异常恢复)。
3. 保护 Row Log 缓冲区不溢出
如果并发 DML 量极大,缓冲区可能被写满。此时,MySQL 会阻塞产生该 DML 操作的会话,直到拷贝线程从缓冲区中消费掉部分记录,腾出空间。
- 衡量标准:当已用空间超过
innodb_online_alter_log_max_size的 90% 时,新 DML 会被暂时挂起。 - 后果:如果你在业务高峰期执行
ALTER TABLE ... ALGORITHM=COPY,并且表写入量很大,可能会导致大量写入超时或堆积。因此建议在低峰期操作,或先用pt-online-schema-change工具(它采用更灵活的触发器方式)。
4. 拷贝线程与日志消费的关系
全量拷贝线程逐行读取旧表数据,并将每一行插入临时表。同时,它也会定期检查 Row Log 缓冲区,将已确认已经拷贝到临时表的行对应的旧日志标记为可重用(空间释放)。
- 动作:拷贝线程每拷贝完一批数据(通常是 1000 行)后,会尝试从缓冲区中取出所有可应用的增量记录,但注意:此时它不会立即应用——它只是做指针移动,防止缓冲区持续增长。实际应用在第三阶段进行。
这里有一个关键点:全量拷贝阶段只记录增量,不应用增量。目的是将全量数据与增量数据分离,保证全量拷贝的线性速度。
应用阶段:将增量合并到新表
全量拷贝完成后,进入第三阶段:
1. 锁定原表
MySQL 对原表加 EXCLUSIVE 锁(阻止所有 DML),确保没有新的增量产生。然后从 Row Log 缓冲区读取所有未消费的变更记录。
2. 按顺序应用 Row Log
将记录按序列号从小到大逐一应用到临时表:
- INSERT:如果临时表中已存在相同主键的行(可能由全量拷贝已经插入),则忽略或根据
ON DUPLICATE KEY逻辑处理;实际上,因为全量拷贝已经包含了该行,所以此处会跳过重复行——但 Row Log 中的INSERT记录一定发生在全量拷贝还未拷贝到该行的时间点吗?不一定,因为拷贝是逐行进行的,拷贝到某行时,如果该行刚被删除,则不会插入;此时 Row Log 中的DELETE记录必须在INSERT之后应用,否则会出现约束冲突。因此应用顺序必须完全按照序列号。 - DELETE:根据主键值删除临时表中的对应行。
- UPDATE:先删除旧版本行(如果主键未变则原地更新),再插入新行。
3. 原子性替换
应用完毕后,重命名原表为备份名,临时表重命名为原表名,然后释放元数据锁。整个过程是原子操作,业务感知到的只是短暂的写阻塞(毫秒级)。
关键细节与常见陷阱
1. Row Log 缓冲区大小不足
如果 innodb_online_alter_log_max_size 设置的过小(比如默认的 128MB 对于大表频繁写入可能不够),全量拷贝阶段中会出现大量的 DML 阻塞,甚至导致 ER_ALTER_OPERATION_NOT_SUPPORTED_REASON 错误(缓冲区溢出且无法继续)。
解决方法:在 ALTER TABLE 语句前,适当增大该参数。
SET SESSION innodb_online_alter_log_max_size = 1073741824; -- 1GB
ALTER TABLE t1 ADD INDEX idx_col1 (col1), ALGORITHM=COPY;
2. 主键变更时的特殊行为
如果 Online DDL 操作修改了表的主键(如 ALTER TABLE ... DROP PRIMARY KEY),则全量拷贝阶段旧表的主键会被当作普通行记录,Row Log 中记录的键值会使用旧的主键。应用阶段需要根据新主键重新映射,这可能导致额外的日志处理开销。
3. 与 pt-online-schema-change 的对比
MySQL 原生的 Online DDL(COPY)使用 Row Log 是内部机制,完全由 InnoDB 引擎管理,无需创建触发器。而 pt-online-schema-change 使用触发器捕获增量,适用于更复杂的场景(如不支持 Online DDL 的版本或操作)。两者原理类似,但 MySQL 原生方案更轻量、无触发器性能损耗。
总结
MySQL Online DDL 在全量拷贝阶段通过 Row Log 缓冲区 记录所有并发的 DML 变更,并在全量拷贝完成后按序应用。这个机制保证了最终数据的一致性,同时最大程度降低了锁持有时间。理解 Row Log 的大小限制和阶段划分,能帮助你更平稳地执行大表结构变更。
(无需额外总结,正文到此结束。)

暂无评论,快来抢沙发吧!