背景
当一张表的数据量达到千万级甚至亿级时,直接执行 ALTER TABLE ADD COLUMN 可能会带来严重问题:
- 锁表:传统 DDL 操作期间表被锁定,无法读写
- 耗时长:大表 DDL 可能需要数小时甚至更久
- 主从延迟:DDL 在主库执行完后才开始在从库执行,导致从库严重落后
- 业务中断:上述问题叠加可能导致线上故障
本文梳理 MySQL 各版本的改进以及主流的快速添加字段方案。
一、MySQL 原生方案演进
1.1 MySQL 5.5 及以前:COPY 算法
| |
默认使用 COPY 算法:创建临时表 → 复制数据 → 替换原表。全程锁表,大表基本不可用。
1.2 MySQL 5.6+:Online DDL(INPLACE)
MySQL 5.6 引入了 Online DDL,支持 ALGORITHM=INPLACE,允许 DDL 期间并发 DML 操作:
| |
关键参数:
| 参数 | 说明 |
|---|---|
ALGORITHM=INPLACE | 在原表上直接修改,避免全表复制 |
LOCK=NONE | 允许并发读写 |
LOCK=SHARED | 允许读,阻塞写 |
LOCK=EXCLUSIVE | 阻塞读写 |
注意事项:
- ADD COLUMN 在 INPLACE 模式下仍需要重建表(全表拷贝),只是不锁表
- 执行期间会消耗额外的磁盘空间(临时表)
- 对于超大表,INPLACE 模式依然很慢
1.3 MySQL 8.0.12+:Instant ADD COLUMN
MySQL 8.0.12 引入了 ALGORITHM=INSTANT,只需修改表的元数据,不涉及数据拷贝,执行时间是毫秒级:
| |
这是最理想的方案。 但 Instant ADD COLUMN 有严格的限制条件:
支持的场景:
- ✅ 添加列(ADD COLUMN),且添加到表的末尾
- ✅ MySQL 8.0.12+ 添加列到末尾
- ✅ MySQL 8.0.29+ 支持添加列到任意位置
不支持的场景:
- ❌ 添加列的同时添加索引
- ❌ 列涉及外键
- ❌ 表使用了
ROW_FORMAT=COMPRESSED - ❌ 表含有全文索引
- ❌ 临时表(TEMPORARY TABLE)
- ❌ 数据字典中的表
检查当前表是否支持 INSTANT:
| |
二、第三方工具方案
当 MySQL 版本不支持 INSTANT,或场景受限时,第三方工具是首选。
2.1 gh-ost(GitHub 开源)
gh-ost 是 GitHub 开发的无触发器在线表变更工具(⭐ 13,300+ Stars)。
核心原理:
- 创建一张与原表结构相同的幽灵表(ghost table)
- 在幽灵表上执行 DDL 变更
- 通过解析 binlog 获取增量变更,应用到幽灵表
- 数据同步完成后,通过 RENAME TABLE 原子交换两张表
优势:
- 无触发器:不使用触发器,对主库性能影响极小
- 可暂停/恢复:随时可以暂停和恢复迁移
- 可回滚:支持
--test-on-replica先在从库测试 - 动态调参:运行时可以调整 chunk 大小和 throttle 策略
- 可审计:所有操作通过 binlog,可追踪
基本用法:
| |
关键参数:
| 参数 | 说明 |
|---|---|
--allow-on-master | 允许在主库上执行(默认连从库) |
--initially-drop-ghost-table | 如果之前有残留的幽灵表,先删除 |
--chunk-size=1000 | 每次迁移的行数 |
--max-load=Threads_running=25 | 负载阈值,超过则暂停 |
--throttle-control-replicas | 指定监控延迟的从库 |
--postpone-cut-over-flag-file | 创建此文件则暂停最终的 rename 操作 |
暂停和恢复:
| |
2.2 pt-online-schema-change(Percona Toolkit)
Percona 出品的经典在线表变更工具,使用触发器实现增量同步。
核心原理:
- 创建一张与原表结构相同的新表
- 在新表上执行 DDL
- 在原表上创建 AFTER INSERT/UPDATE/DELETE 触发器,将变更同步到新表
- 分块(chunk)复制原表数据到新表
- 数据同步完成后 RENAME TABLE 交换
基本用法:
| |
注意:
- 使用触发器,对高并发写入场景性能影响较大
- 外键约束需要额外处理(
--alter-foreign-keys-method) - 如果表已有触发器,可能冲突
2.3 两者对比
| 维度 | gh-ost | pt-osc |
|---|---|---|
| 增量同步方式 | 解析 binlog | 触发器 |
| 主库性能影响 | 极小 | 较大(触发器开销) |
| 可暂停/恢复 | ✅ 原生支持 | ❌ 不支持 |
| 外键支持 | 需手动处理 | --alter-foreign-keys-method |
| 并发写入场景 | ✅ 适合 | ⚠️ 触发器可能导致锁争用 |
| 社区活跃度 | ⭐ 13,300+ | Percona 维护 |
| 学习成本 | 中等 | 较低 |
选择建议:
- 高并发写入环境 → gh-ost(无触发器)
- 已有 Percona Toolkit 经验 → pt-osc(上手简单)
- 需要精细控制 → gh-ost(支持暂停、throttle)
三、手工替换表法
适用于没有第三方工具、又不满足 INSTANT 条件的场景。
步骤:
| |
风险:
- RENAME 期间有短暂不可写窗口
- 需要处理增量数据的一致性
- 磁盘空间翻倍
四、方案决策树
| |
五、生产环境操作建议
5.1 操作前准备
| |
5.2 操作中监控
| |
5.3 回滚方案
| 方案 | 回滚方式 |
|---|---|
| INSTANT | ALTER TABLE DROP COLUMN(同样是 instant) |
| gh-ost | 重新执行一次 gh-ost,alter 改为 DROP COLUMN |
| pt-osc | 重新执行一次 pt-osc,alter 改为 DROP COLUMN |
| 手工替换 | RENAME TABLE big_table TO big_table_new, big_table_old TO big_table |
六、总结
| 方案 | MySQL版本 | 执行时间 | 锁表 | 复杂度 |
|---|---|---|---|---|
| INSTANT | 8.0.12+ | 毫秒级 | 不锁 | 最低 |
| Online DDL | 5.6+ | 分钟~小时 | 不锁表但阻塞部分操作 | 低 |
| gh-ost | 5.6+ | 分钟~小时 | 不锁 | 中 |
| pt-osc | 5.6+ | 分钟~小时 | 不锁 | 中 |
| 手工替换 | 全版本 | 小时级 | 短暂锁 | 高 |
核心建议:能升级到 MySQL 8.0 就升级,大多数 ADD COLUMN 操作用 INSTANT 秒级完成。不能升级的,用 gh-ost 是最稳妥的选择。