7AA7 Blog

数据库迁移:跨库数据同步与一致性校验

雾梨 Mist UID:84
2026-09-28 12:23
36 0 1

迁移的困难不在搬数据,而在搬的过程中源库还在被写入。两边同时有写入,就必须在”业务不能停”和”数据不能错”之间找一条可执行路径。所有迁移方案本质上都在安排这两件事的顺序。

三类场景,难度差很多

场景 典型动因 主要难点 可行路径
同构升级 版本升级、硬件更换、上云 停机窗口、版本兼容性 主从切换,相对简单
异构迁移 换数据库类型、换存储引擎 类型映射、SQL 语义差异、性能特性变化 双写 + 校验
分库拆分 单库容量或写入瓶颈 分片键选择、跨片查询与事务 双写 + 路由改造

同构升级通常借助数据库自身的复制能力:搭从库追上主库,延迟归零后切换。问题集中在窗口大小和版本兼容性,数据本身不需要重新解释。

异构迁移要处理语义差异:同样的 datetime 在不同数据库里精度和范围不同,同样的分页深翻行为不同,事务隔离级别的实现也不同。这些问题在双写阶段不暴露,往往等切读之后才冒出来。

分库拆分最难的是分片键。它决定哪些查询能落单分片、哪些必须跨片聚合,而这个决定在迁移完成后很难改——改分片键等于再搬一次数据。

双写的四个阶段

异构迁移和分库拆分通常用双写,它的阶段划分是固定的:

一、增量双写。 应用同时写源库和目标库,读仍走源库。目标库此时只有增量数据。这一步要先上线观察一段时间——双写的失败率、延迟、对源库写入路径的影响都要先看清楚。

二、存量搬迁。 把历史数据搬到目标库。这一步要限速,否则会打满源库或网络。

三、校验。 对比两边数据,修复差异。

四、切读。 把读流量切到目标库,保持双写。观察一段时间确认无问题。

五、停双写。 只写目标库,源库下线或只读保留。

顺序约束是硬的:双写必须先于存量搬迁开启,否则搬迁期间的增量会丢失;停双写必须在切读稳定运行一段时间之后,因为双写是回滚的安全网,提前停掉发现问题就回不去了。

写入语义也要提前定清楚:源库成功目标库失败时怎么办?常见做法是记录失败并异步补偿,同时监控失败量;直接对业务报错会让迁移风险传导到用户,通常不可取。

存量搬迁:分批与限速

-- 按主键分批,每批取一段,避免大范围扫描
SELECT * FROM orders
WHERE id > ? AND id <= ?
ORDER BY id;

分批依据最好是主键或有索引字段,每批大小要实测确定——批太小总时间被往返次数拖长,批太大在源库上产生大事务和长锁。

限速手段通常是批间延时与并发度控制。搬迁任务要有可动态调整的速率参数,因为源库的实际承受能力常与预估不同。还要能从断点继续:记录已搬迁的最大 ID,中断后接着走,而不是从头再来。

时间上避开业务高峰。搬迁对源库的影响不只是读,还有网络出口带宽和缓冲池污染——大量扫描把热数据挤出缓存,导致在线查询变慢。这一点常被忽略,表现为”迁移一开始线上查询就慢了”。

校验的三个层次

层次 做法 成本 能发现什么
行数比对 分别 count 两边 低 漏搬、重复搬
抽样字段比对 随机抽取主键,逐字段比 中 字段映射错误、精度丢失
全量校验和 按区间算哈希或校验和对比 高 任何内容差异

行数比对最便宜也最粗略,发现不了”行数对但内容错”。抽样比对能覆盖大部分映射错误,代价是总会有漏网。全量校验和能给出确定结论,代价是扫两边全表,对源库和目标库都有压力。

实用组合是:先用行数快速筛,再跑抽样,然后对关键表或抽样异常的表做全量。全量校验和要按主键区间分块计算、逐块比对,这样能定位到具体区间,而不是得到”整体不一致”的结论。

校验要在搬迁完成后、切读之前做,切读后再做一次增量校验。切读之后源库仍在被写,第二次校验验证的是切换期间是否产生了分叉。

不一致怎么处理

先定原则:以源库为准还是以目标库为准。多数情况下是源库——它是权威数据,目标库是副本。但原则有例外:如果目标库已承接了一部分写入(切读后业务基于它产生新数据),就不能简单用源库覆盖。

处理流程建议:

  1. 统计差异规模与分布:集中在某时间段、某分片还是某类字段?集中分布通常指向一个具体 bug,均匀分布更可能是映射问题。
  2. 判断差异类型:缺行、值错、精度、字符集问题,处理方式不同。
  3. 小规模差异定点修复,大规模差异修好原因后重跑那一批。
  4. 设定人工介入阈值:差异率超比例就停下来查原因,不要边修边切。

差异修复要有记录,修完重新校验,直到该区间干净。

回滚方案必须提前准备

回滚的前提是双写还在。只要双写没停,切读就可以切回源库,这是最有力的安全网,所以停双写放在最后并留足观察期。

回滚要提前准备细节:切回的开关在哪、多久生效、切回后目标库多出的数据怎么处置。最麻烦的是切读期间业务在目标库写入了新数据,回滚时源库没有——要么保证切读期间仍双写且源库可写,要么提前想清楚合并方式。

回滚方案要写成可执行清单,而不是”大不了切回去”。真出问题时没人有时间重新设计回滚路径。

切换的灰度与观察窗口

读流量切换要灰度:先 1% 观察,再 10%,再全量。每档之间留观察窗口,看错误率、延迟,以及业务指标(下单成功率、退款笔数这类)。技术指标正常不代表业务正常——查询慢 200ms 可能不影响可用性,却明显影响转化。

观察窗口要覆盖一个完整业务周期,至少包含一天的高峰与低峰。只观察两小时就全量,会错过定时任务、报表批处理这类只在特定时段运行的负载。

常见陷阱

自增 ID 冲突。 目标库已有数据,再按自增主键插入可能与存量撞号,或两边各自分配导致合并后冲突。解决办法是提前规划 ID 空间,或换成分布式 ID 生成。

时间字段精度。 源库存到秒、目标库存到毫秒或反过来。搬迁后同一条记录的时间字段不匹配,校验时表现为”处处不一致”,排查很费时间。

字符集与排序规则。 同样存字符串,字符集不同会导致长度限制、比较结果、索引命中都不同。

时区。 时间字段带时区与否、会话时区设置、应用连接参数,三者不一致会让时间整体偏移几小时,且只在部分查询里表现出来。

NULL 与空串。 有些数据库里两者等价,有些不等价。搬迁后 WHERE col IS NULL 的结果变了,业务逻辑跟着变。

大字段与特殊类型。 JSON、二进制、空间类型在异构迁移时往往没有直接对应物,需要提前定映射规则并验证。

这些陷阱的共同点是在小规模试迁时常常不出现,等全量跑完才暴露。正式搬迁前用一小批真实数据走完整流程演练一次,成本远低于事后修复。

630Views
36Likes
0Comments
0Participants
7 minread time

Comments (0)

No comments yet — be the first.