数据库迁移的5种策略,以及我踩过的3个坑

数据库迁移的5种策略,以及我踩过的3个坑

——从"删库跑路"到"丝滑上线"的血泪史


前言

如果说微服务拆分是一场豪赌,那数据库迁移就是赌桌上最危险的那个筹码。

代码出问题了,回滚就行。但数据库呢?数据丢了就是丢了,回滚可能意味着用户账户余额归零、订单记录消失、聊天记录蒸发。

我职业生涯里经历过 4 次比较大规模的数据库迁移,从 MySQL 5.6 升 8.0、从单库拆分成分库分表、从自建机房迁到云上、从关系型数据库迁到 NewSQL。其中有两次堪称"惊魂夜",至今想起来手心还冒汗。

这篇文章,我把做过的方案和踩过的坑都摊开来聊。


一、5种数据库迁移策略

先说清楚一个前提:没有"最好"的迁移策略,只有"最适合你当前约束"的策略。你的停机窗口、数据量、一致性要求、团队能力,决定了该选哪条路。


策略一:停机迁移(Big Bang)

做法: ​ 停服 → 导出数据 → 导入新库 → 校验 → 切流量 → 开服。

旧库 ──(dump)──> 新库 ──> 切流量 ──> 上线

优点:

  • 方案最简单,几乎不需要额外的同步工具
  • 数据一致性最容易保证(停机期间没有写入)
  • 出问题了,旧库还在,回滚就是切回去

缺点:

  • 需要停机,时间取决于数据量
  • 数据量越大,停机时间越长,业务方越不爽

适用场景:

  • 数据量小(GB 级别),导出导入能在可接受时间内完成
  • 业务允许短暂停机(比如凌晨维护窗口 2 小时)
  • 一次性迁移,不需要长期双写

我的判断标准: ​ 如果导出+导入+校验能在 30 分钟内完成,停机迁移是最省心的选择。超过 30 分钟,就要认真考虑下面的方案了。


策略二:双写迁移(Dual Write)

做法: ​ 应用同时往新旧两个库写数据,读流量逐步从旧库切到新库。

应用 ──写入──> 旧库
  │
  └──写入──> 新库

读流量: 旧库 100% → 90% → 50% → 10% → 0%

优点:

  • 不停机,对用户无感
  • 可以随时回滚(把读流量切回旧库)
  • 新库可以灰度验证

缺点:

  • 应用代码要改,写入逻辑变复杂
  • 双写期间数据可能不一致(比如写旧库成功、写新库失败)
  • 迁移周期长,代码里要维护两套写入逻辑,容易遗留"技术债"

适用场景:

  • 不允许停机
  • 数据量中等到大(TB 级别)
  • 新旧库的数据模型差异不大(不需要复杂的转换)

关键细节: ​ 双写不是"同时写两个库"那么简单。你需要考虑:

  • 写失败了怎么办?重试?补偿?
  • 两个库的写入顺序(先写哪个?如果第一个成功第二个失败,怎么处理?)
  • 是否需要分布式事务(通常不建议,太重了)

策略三:CDC 增量同步(Change Data Capture)

做法: ​ 利用数据库的 binlog / WAL / 变更日志,实时捕获增量变更,同步到新库。先做全量迁移,再做增量追平,最后切流量。

旧库 ──binlog──> CDC工具(如Debezium/Canal) ──> 新库

优点:

  • 对应用代码零侵入(不需要改业务代码)
  • 可以做到秒级延迟的实时同步
  • 迁移期间旧库继续提供服务,不影响业务

缺点:

  • 引入新的中间件,运维复杂度增加
  • 需要处理 DDL 变更、大事务、循环写入等边界情况
  • 全量+增量追平的时间窗口可能很长

适用场景:

  • 数据量巨大(TB ~ PB 级别)
  • 不允许停机
  • 团队有能力运维 CDC 工具
  • 迁移周期可以比较长(几天到几周)

常用工具: ​ Debezium、Canal、Maxwell、AWS DMS、阿里云 DTS。


策略四:影子表迁移(Shadow Table)

做法: ​ 在同一个数据库实例中,创建新结构的"影子表",通过触发器或应用层同步数据,验证通过后切换表名。

原表: users
影子表: users_new (新结构)
切换: RENAME TABLE users TO users_old, users_new TO users

优点:

  • 切换瞬间完成(RENAME 是原子操作)
  • 不需要额外的数据库实例
  • 回滚也快(再 RENAME 回去)

缺点:

  • 触发器会影响写入性能
  • 不适合跨数据库引擎的迁移
  • 表特别大时,RENAME 也可能有短暂锁表

适用场景:

  • 表结构变更(加字段、拆表、改索引)
  • 数据量中等
  • 新旧表在同一个数据库实例中

策略五:绞杀者模式(Strangler Fig Pattern)

做法: ​ 不一次性迁移,而是逐步把某些表/功能模块迁移到新库,旧库和新库长期共存,通过 API 层或代理层路由。

请求 ──> API网关 ──> 旧库 (用户模块)
                  ──> 新库 (订单模块)

优点:

  • 风险最低,每次只迁移一小部分
  • 可以边迁移边验证
  • 回滚粒度小(只回滚某个模块)

缺点:

  • 系统长期运行在"混合态",运维心智负担大
  • 跨库 JOIN 和事务处理复杂
  • 迁移周期可能长达数月甚至更久

适用场景:

  • 超大型系统,不可能一次性迁移
  • 微服务拆分 + 数据库拆分同时进行
  • 组织有能力管理长期双库共存

二、我踩过的3个坑

理论说完了。现在来说说我亲身经历的三个坑。每一个都让我至少熬了一个通宵。


坑一:binlog 追不上,迁移窗口无限延长

背景: ​ 我们从一个单库 MySQL 迁移到一个分库分表的新集群。数据量大约 2TB,QPS 峰值 5000+。

方案: ​ CDC 增量同步。全量导出用了 8 小时,然后开始追 binlog 增量。

问题: ​ 全量导出的 8 小时里,线上产生了大量写入。CDC 工具追增量的速度大约是每小时处理 2 小时的 binlog。也就是说,增量追平需要 16 小时

但我们以为几个小时就能追平,所以没准备足够的停机窗口。结果到了预定切换时间,binlog 还差 12 个小时的量没追完。

更糟的是: ​ 随着时间推移,线上写入还在继续,binlog 越积越多。CDC 工具的消费速度开始跟不上生产速度,延迟从 2 倍变成了 3 倍、4 倍。

那一刻我意识到:我们陷入了"追赶陷阱"。

怎么解决的: ​ 最终我们不得不临时扩容 CDC 的消费端,加了 4 个消费节点并行处理,同时跟业务方协商,在凌晨低峰期短暂限流写入。花了 6 个小时才追平。

教训:

  1. 全量迁移期间,估算 binlog 增量时,要按峰值 QPS 算,不要按平均值算。
  2. 如果全量迁移时间超过 4 小时,提前准备"追赶方案"——多消费节点、临时限流、或者考虑分表并行迁移。
  3. 永远假设 CDC 追增量会比你预期慢。 ​ 给它预留 2-3 倍的缓冲时间。

坑二:字符集不一致,中文数据变乱码

背景: ​ 从自建 MySQL 5.6 迁移到云上 RDS MySQL 8.0。

问题: ​ 迁移完成后,测试发现部分中文数据变成了乱码。排查发现:

  • 旧库的字符集是 utf8(注意,MySQL 的 utf8 不是真正的 UTF-8,它最多支持 3 字节,存不了 emoji 和一些生僻字)
  • 新库的字符集是 utf8mb4
  • 导出时用的工具默认按旧库字符集导出,但导入时新库按 utf8mb4 解析,导致部分 4 字节字符被截断或替换

更隐蔽的是: ​ 不是所有中文都出问题,只有那些包含 emoji 或者某些特殊字符的记录才中招。所以第一轮冒烟测试没发现,到了 UAT 阶段才被用户报出来。

怎么解决的: ​ 重新导出,指定 --default-character-set=utf8mb4 参数,确保导出和导入的字符集一致。然后写了个脚本扫描所有文本字段,找出可能损坏的记录,从旧库重新同步。

教训:

  1. 迁移前,先 SHOW CREATE TABLESHOW VARIABLES LIKE 'character_set%' 确认字符集。
  2. 导出和导入工具要显式指定字符集,不要依赖默认值。
  3. 数据校验不要只抽样,要全量比对关键字段。 ​ 至少对文本字段做长度校验和内容哈希校验。

坑三:外键约束导致切换后写入失败

背景: ​ 把几个关联表从一个库迁移到另一个库(跨库迁移,不是同实例)。

问题: ​ 旧库里有外键约束。迁移时,我按照表的大小顺序导出导入——先导小表,再导大表。看起来没问题。

但切换后,应用开始报外键约束错误:Cannot add or update a child row: a foreign key constraint fails

原因: ​ 切换时,旧库还在接受写入。我做了最后一轮增量同步,但同步的顺序是先同步子表,再同步父表。结果父表里有一条新数据还没同步过来,子表就已经引用了它的 ID。

更糟的是: ​ 这个问题不是立刻暴露的。切换后的前 10 分钟一切正常,然后随着新写入积累,错误越来越多。

怎么解决的: ​ 紧急回滚,把流量切回旧库。然后重新设计同步顺序——先同步父表,再同步子表。并且切换时短暂停止相关表的写入(用只读模式),确保最后一轮增量同步完成后,新旧库数据一致,再切流量。

教训:

  1. 有外键约束时,同步顺序必须严格遵守依赖关系。 ​ 父表先于子表。
  2. 切换窗口内,考虑对相关表加写锁或进入只读模式,哪怕只有 1-2 分钟。
  3. 切换后的前 30 分钟是最危险的。 ​ 这段时间要盯着错误日志和监控,不要以为"能访问了"就万事大吉。

三、总结:我的迁移检查清单

经历了这些之后,我现在每次做数据库迁移,都会过一遍这个清单:

迁移前

  • 确认数据量和停机窗口的匹配度
  • 确认字符集、排序规则、时区设置
  • 确认外键、触发器、存储过程、事件调度器
  • 备份!备份!备份!(并且验证备份能恢复)
  • 准备回滚方案,并且演练一次

迁移中

  • 全量导出导入的进度监控
  • CDC 增量追赶的速度 vs 生产写入速度,实时对比
  • 切换窗口的写入控制(限流/只读/短暂停写)

迁移后

  • 数据校验(行数、关键字段哈希、抽样比对)
  • 应用日志监控(前 30 分钟最关键)
  • 性能指标对比(响应时间、慢查询)
  • 旧库保留至少 7 天(不要急着删)

最后

数据库迁移这件事,说难也难,说简单也简单。难的不是技术,是敬畏心。

每一次迁移,本质上都是在跟数据的安全性博弈。你多一分准备,数据就少一分风险。

我踩过的坑,希望你能绕过去。如果绕不过去——至少,别在凌晨三点才发现。

0
0
0
0
评论
未登录
暂无评论