为超过10亿条记录的订单表新增字段

作者:Sam_Deep_Thinking日期:2025/10/26

如何为 10 亿行 MySQL 大表安全新增字段?

之前在一家餐饮公司待过几年。由于这家公司已经营业了很多年,MySQL 5.6 的订单表数据早已超过 10 亿行。这就带来一个经典难题:

如何为这张大表新增字段?

当时的处理方式

假设业务需求的上线时间是 2025 年 10 月 23 日,我们会安排组内同事在 10 月 22 日或 23 日凌晨 提交数据库变更:

1ALTER TABLE order_items ADD COLUMN new_flag TINYINT DEFAULT 0;
2
  • 变更通常需要 4~6 小时 才能完成;
  • 期间表会被锁住,无法写入;
  • 但因为是 凌晨闭店时段,门店基本无流量,影响可控。

这是当时的最佳方案吗?

答案是:对的。

原因如下:

  • 我们使用的是 MySQL 5.6,没有 Instant ADD COLUMN 功能(该功能从 MySQL 8.0.12 才引入);
  • 当时使用的是 腾讯云 MySQL,其“无锁结构变更”功能尚未对外开放;
  • 团队(开发 + DBA)不具备使用 gh-ost 等工具的能力和经验
  • 业务低峰期(凌晨闭店)提供了操作窗口

因此,只能使用 MySQL 原生 DDL,注定缓慢但安全。


那现在呢?有更好的方案吗?

当然有! 主要分为两种情况:


方案一:利用 MySQL 8 的 Instant ADD COLUMN(即时加列)

原理

MySQL 8.0.12 开始,支持 Instant ADD COLUMN,其核心原理是:

  • 不重建表(no table rebuild)
  • 不复制数据(no data copy)
  • 仅修改表元数据(.sdi 文件)
  • 新增列的默认值通过 隐藏的默认值记录 实现,读取旧行时动态填充

✅ 仅适用于:在表末尾添加可为空或带默认值的列


传统加列 vs 即时加列:根本区别

对比项传统加列(MySQL 5.7 / 8.0 早期)即时加列(Instant ADD COLUMN)
是否重建表✅ 是(全表拷贝)❌ 否
是否读写每一行数据✅ 是❌ 否
是否修改 .ibd 数据文件✅ 是❌ 否
耗时小时级(10亿行)毫秒级(< 100ms)
锁表时间长(写锁数小时)极短(元数据锁 < 10ms)

即时加列的底层原理

核心思想:延迟填充(Lazy Evaluation)

MySQL 不在 DDL 时写默认值到每一行,而是在 读取旧行时动态填充默认值

实现机制:
  1. 表的元数据中记录“新增列的默认值”
    • 存储在数据字典(MySQL 8.0 使用 InnoDB 存储数据字典)
    • 每个 Instant 列都有一个“隐藏的默认值记录”
  2. 物理数据行(.ibd 文件)完全不变
    • 旧数据行仍保持原来的格式和长度
    • 不占用额外磁盘空间
  3. 查询时动态补值
    • SELECT 读取一行时,InnoDB 发现该行“缺少新列”
    • 自动从元数据中取出默认值,返回给 Server 层
    • 应用层完全无感

📌 举例:

1-- 原表有 10 亿行,无 new_flag 列
2ALTER TABLE order_items ADD COLUMN new_flag TINYINT NULL DEFAULT 0; -- Instant
3
4SELECT new_flag FROM order_items WHERE id = 1;
5-- InnoDB 返回:0(即使物理行中根本没有这个字段)
6

元数据如何记录?

MySQL 8.0 引入了 “Instant Column Metadata”

  • INFORMATION_SCHEMA.INNODB_TABLES.instant_cols 中记录 Instant 列数量
  • 在表的 SDI(Serialized Dictionary Information) 中存储每个 Instant 列的:
    • 名称
    • 类型
    • 默认值
    • 是否可空

💡 这些信息只占几 KB,修改成本极低。


新写入的行怎么办?
  • 新插入的行包含新列的实际值(或显式默认值)
  • 物理格式会更新,但只影响新行
  • 旧行仍保持原格式,读取时动态补值

✅ 这就是“混合格式”共存,InnoDB 能正确解析。


为什么这么快?关键点总结

原因说明
1. 不读数据不扫描 .ibd 文件,不 touch 任何数据页
2. 不写数据不修改任何数据行,不产生 Redo/Undo
3. 只改元数据仅更新数据字典(内存 + 少量日志)
4. 无 I/O 压力磁盘、CPU、内存消耗几乎为零
5. 锁粒度极小仅短暂持有 MDL(元数据锁),毫秒级

支持条件(必须全部满足)

条件是否必须
MySQL ≥ 8.0.12
新增列在最后一列
列为 NULL 或有 DEFAULT 值
表使用 InnoDB 引擎
不能是 FULLTEXT / SPATIAL 索引表

示例

1-- ✅ 支持 Instant ADD
2ALTER TABLE order_items ADD COLUMN new_flag TINYINT NULL DEFAULT 0;
3
4-- ❌ 不支持 Instant(会触发全表重建)
5ALTER TABLE order_items ADD COLUMN new_flag TINYINT NOT NULL;        -- 无默认值
6ALTER TABLE order_items ADD COLUMN new_flag TINYINT AFTER user_id;  -- 插入中间
7

✅ 如果你的业务数据库已升级到 MySQL 8.0.12+,且满足上述条件,那么在 10 亿行大表上加字段,1 秒内即可完成


方案二:使用 gh-ost(适用于 MySQL 5.6/5.7/8.0 但不满足 Instant 条件)

如果无法使用 Instant DDL(如仍在用 MySQL 5.7),强烈建议使用云厂商的“无锁结构变更”功能(如阿里云 DMS、腾讯云数据库管理),其底层正是基于 gh-ost

gh-ost 原理详解:如何在业务持续写入下安全完成大表结构变更

gh-ost(GitHub Online Schema Change) 是 GitHub 开源的 MySQL 在线 DDL 工具,专为 10 亿级大表 设计,能在 业务系统持续高并发写入 的情况下,无锁、安全、可控地完成表结构变更。


一、核心思想

gh-ost 不依赖 MySQL 原生 DDL,而是通过 “应用层数据迁移 + binlog 实时同步” 的方式,将危险的 DDL 转化为一个 可中断、可限速、可监控的数据同步任务


二、整体流程(含持续写入场景)

假设对 order_items 表执行:

1gh-ost --alter="ADD COLUMN new_flag TINYINT DEFAULT 0" ...
2

gh-ost 执行步骤如下:

1graph TD
2    A[1. 创建影子表 _order_items_gho] --> B[2. 应用新表结构]
3    B --> C[3. 全量分页拷贝原表数据]
4    C --> D[4. 实时监听 binlog]
5    D --> E[5. 将 DML 转换后重放到影子表]
6    E --> F[6. 持续追平延迟]
7    F --> G[7. 毫秒级原子切换]
8    G --> H[8. 清理临时表,任务完成]
9

三、关键机制详解

3.1 影子表(Ghost Table)
  • 名称:_order_items_gho
  • 创建方式:
1CREATE TABLE `_order_items_gho` LIKE `order_items`;  
2ALTER TABLE `_order_items_gho` ADD COLUMN new_flag TINYINT DEFAULT 0;  
  • 不锁原表,瞬间完成

3.2 全量拷贝(Chunked Copy)
  • 按主键分页,逐块拷贝:
1INSERT INTO _order_items_gho (id, order_id, ...)  
2SELECT id, order_id, ...  
3FROM order_items  
4WHERE id BETWEEN 1 AND 10000;  
  • 可限速(如每秒 1000 行),避免打爆 I/O
  • ⚠️ 拷贝期间,原表仍在被业务写入!

3.3 binlog 实时同步(核心!)

gh-ost 以 从库身份连接 MySQL,解析 ROW 格式 binlog,捕获所有对原表的 DML:

原表操作gh-ost 在影子表执行
INSERT INTO order_items (...) VALUES (...)INSERT INTO _order_items_gho (...) VALUES (...)
UPDATE order_items SET price=100 WHERE id=123UPDATE _order_items_gho SET price=100, new_flag=0 WHERE id=123
DELETE FROM order_items WHERE id=456DELETE FROM _order_items_gho WHERE id=456

所有新写入的数据,实时同步到影子表,确保不丢数据


3.4 如何处理“拷贝中 + 写入”的冲突?

场景:全量拷贝进行到 id=5亿,此时业务更新了 id=100

gh-ost 处理逻辑

  1. binlog 捕获到 UPDATE ... id=100
  2. 立即重放到影子表(即使 id=100 还未被全量拷贝)
  3. 后续全量拷贝到 id=100 时,会覆盖旧值
  4. binlog 重放会再次修正为最新值

🔒 通过“最终重放”机制,保证影子表 = 原表最新状态


3.5 最终切换(Cut-over)

当全量拷贝完成 + binlog 延迟 < 1 秒时,gh-ost 发起切换:

  1. 短暂获取排他 MDL 锁(通常 < 100ms)
  2. 执行原子操作:
1RENAME TABLE  
2  order_items TO _order_items_del,  
3  _order_items_gho TO order_items;  
  1. 删除 _order_items_del

🎯 切换瞬间,所有新请求自动打到新结构表上,业务无感


四、为什么能保证“最终一致”?

  • 原表始终是唯一真实数据源
  • 影子表 = 全量快照 + 实时 binlog 重放
  • 切换前确保影子表与原表几乎一致
  • 切换是原子 RENAME,无中间状态

五、失败如何处理?

  • 若 gh-ost 崩溃:
    • 原表 毫发无损
    • 影子表 _order_items_gho 可手动删除
    • 重新启动 gh-ost,支持 断点续传(resume)

六、与原生 DDL 对比优势

能力原生 DDL(MySQL 5.7)gh-ost
是否锁表✅ 长时间写锁❌ 仅切换时毫秒锁
是否可限速❌ 否✅ 是
是否可暂停❌ 否✅ 是
是否支持 5.7❌ ADD COLUMN 必重建✅ 完全支持
失败回滚成本高(需重建回滚)低(删影子表即可)

七、总结

gh-ost 的本质:用“数据迁移”绕过 MySQL DDL 的锁与重建问题。

即使系统 每秒写入 1 万条订单,只要 binlog 能跟上,gh-ost 就能:

  • 不锁表
  • 不丢数据
  • 最终一致
  • 安全完成 10 亿级大表变更

⚠️ 注意:gh-ost 虽然“无锁”,但仍需数小时完成数据迁移(取决于数据量和限速策略),只是不影响业务写入。它解决的是“可用性”问题,而非“速度”问题。


最终建议

场景推荐方案
MySQL ≥ 8.0.12 + 满足 Instant 条件✅ 直接使用原生 ALTER TABLE(秒级完成)
MySQL 5.6 / 5.7 / 8.0(不满足 Instant)✅ 使用云厂商“无锁变更”(基于 gh-ost)
自建数据库 + 有运维能力✅ 自行部署 gh-ost 或 pt-online-schema-change

技术在进步,曾经的“无奈之举”,如今已有优雅解法。


为超过10亿条记录的订单表新增字段》 是转载文章,点击查看原文


相关推荐


C 语言标准库头文件 time.h
hubenchang05152025/10/23

#C 语言标准库头文件 time.h 请查看 C 语言标准库头文件列表 了解更多相关 API。 这个头文件提供 线程 相关的功能。例如线程控制、互斥量、条件变量、线程局部存储等。 #常量 宏标准说明CLOCKS_PER_SECC89处理器的每秒时钟数,POSIX 上定义为 1000000 而非实际的时钟数TIME_UTCC11表示时间基准为 UTC 时间 #类型 类型标准说明time_tC89日历时间的时间戳类型(自 1970 年 1 月 1 日 00:00:00.000 UTC 以来的秒数


Redis(77)Redis缓存的优点和缺点是什么?
Victor3562025/10/22

Redis作为一个高性能的分布式缓存系统,具有许多优点,但也有一些缺点。以下详细讲解Redis缓存的优点和缺点,并结合代码说明如何尽量发挥其优点和规避其缺点。 优点 1. 高性能 Redis以其卓越的读写性能著称,能够支持高吞吐量的读写操作。 示例代码: import redis.clients.jedis.Jedis; public class RedisPerformanceExample { public static void main(String[] args) {


FFmpeg 基本数据结构 AVFormatConext 分析
给大佬递杯卡布奇诺2025/10/21

1、FFmpeg 主要数据结构分层设计 1.1 IO抽象层 协议层与 I/O 抽象层 (Protocol & I/O Abstraction),这一层负责从最广泛的数据源读取或写入数据。 核心数据结构:AVIOContext 功能: 抽象了底层的 I/O 操作。通过它,FFmpeg 可以用统一的接口处理文件、网络流(HTTP, RTMP, TCP)、内存缓冲区等。 关键点: 它使得上层的格式层(解复用)无需关心数据是从哪里来的。这对于播放网络直播流或处理内存中的媒体数据至关重


SpringCloud微服务项目实战——系统实现篇
thginWalker2025/10/20

06 服务多不易管理如何破——服务注册与发现 经过上一篇系统性的介绍 Spring Cloud 及 Spring Cloud Alibaba 项目,相信你已经对这两个项目有个整体直观的感受,本篇开始正式进入本课程的第二部分,一起进入业务的开发阶段。 服务调用问题 在分析业务需求时,其中有个简单的功能点:会员可以开通月卡,开通月卡的同时,需要增加相应的积分。开通月卡功能在会员服务模块维护,但增加积分功能在积分服务模块维护,这就涉及到两个模块间的服务调用问题。 单实例情况:可以采用点对点的


linux系统jdk&&mysql配置
阑梦清川2025/10/18

使用ubuntu进行举例说明: 更新软件包 sudo apt update 安装JDK sudo apt install openjdk-17-jdk 关于mysql, #查找安装包 apt list |grep "mysql-server" #安装mysql sudo apt install mysql-server 查看数据库的状态: sudo systemctl status mysql 如果遇到问题,参考下面的这个: 这个主要是刷新权限表,然后修改我们的密码,否则会报错


【机器学习入门】8.1 降维的概念和意义:一文读懂降维的概念与意义 —— 从 “维度灾难” 到低维嵌入
做科研的周师兄2025/10/17

对于刚入门机器学习的同学来说,“高维数据” 是很容易遇到的痛点 —— 比如处理包含几十甚至上百个特征的数据集时,不仅训练速度变慢,模型还可能因为 “维度太多” 出现泛化能力下降的问题。而 “降维” 正是解决高维数据困境的核心技术。今天我们就从基础概念出发,拆解 “维度灾难” 的危害、降维的本质,以及经典的低维嵌入方法,帮你彻底理解降维为什么重要、到底在做什么。 一、先搞懂:什么是 “维度”?为什么会有 “维度灾难”? 在学习降维前,我们需要先明确 “维度” 的定义,以及高维数据会带来的核心问


【鸿蒙生态共建】鸿蒙6适配-API变化与兼容(2.UI交互与基础能力篇)--《精通HarmonyOS NEXT :鸿蒙App开发入门与项目化实战》读者福利
俩毛豆2025/10/15

HarmonyOS开发者版本6.0.0(API 20)Release(鸿蒙6) 在2005年9月25日正式发布。相对于HarmonyOS 5.0版本增加了多项新能力及对已有能力的优化。同时也有部分API进行了变更。 本篇内容是《精通HarmonyOS NEXT :鸿蒙App开发入门与项目化实战》这本书整体内容的延续,是咱这本书读者的福利,内容以本书的示例作为基础,适配鸿蒙6过程中,所需要完成的调整。并将这些调整记录分享给大家,本篇内容主要介绍与UI交互与基础能力的鸿蒙6兼容适配。 打


2025年下半年加密货币市场展望:机遇与挑战并存
终端域名2025/10/14

一、核心机遇:技术融合与市场结构升级 现实世界资产(RWA)代币化加速 市场规模爆发:截至2025年6月,链上RWA总价值已超238亿美元(不含稳定币),涵盖私人信贷、美国国债、大宗商品等领域。贝莱德预测,到2030年RWA市场规模将飙升至16万亿美元,成为DeFi与传统金融融合的关键桥梁。生态主体壮大:发行机构、托管银行、投资者、区块链平台等主体快速聚集,推动资产运营、跨境支付等领域投资机遇。例如,高盛推出RWA平台,某欧洲奢侈品牌通过NFT分割限量版手表所有权,拓宽市场覆盖面。技术支撑


Redis(59)Redis的主从复制是如何实现的?
Victor3562025/10/13

Redis 的主从复制(Master-Slave Replication)是一种数据复制机制,通过它可以将一个 Redis 实例的数据复制到一个或多个从节点。这种机制可以用于数据备份、读写分离、灾难恢复等场景。以下是 Redis 主从复制的详细实现过程及其代码示例。 主从复制原理 主节点(Master):负责处理写操作(SET、DEL 等),并将数据变化同步到从节点。 从节点(Slave):负责处理读操作(GET 等),从主节点接收数据变化。 同步过程: 初次同步:从节点连接到主节点,主节点


全面解析java注解
学到头秃的suhian2025/10/11

一.注解的定义与分类 注解的概念: Java提供了一种原程序中的元素关联任何信息和任何元数据的途径和方法 JDK自带注解: @Override  代表子类重写父类的方法 @Deprecated  代表该方法已经过时 @SuppressWarning 代表忽略警告Warnings 注解的分类: 源码注解:注解只在源码中存在,编译成.class文件就不存在了编译时注解:注解在源码和.class文件中都存在(@Override,@Deprecated这些都是)运行时注解

首页编辑器站点地图

Copyright © 2025 聚合阅读

License: CC BY-SA 4.0