Skip to content

数据迁移与切库方案 ​

定位:工程实践方法论(工程方法论系列收官篇之一)。换库、分库分表落地、机房搬迁、大表加字段,本质都是同一件事。这件事是:在不停止写入的前提下,把数据与流量从一个"真相源"搬到另一个。迁移方案的全部设计目标只有一个——每时每刻都知道当前以谁为准、出事能退回哪里。与 数据库怎么分库分表(为什么迁)互链。与 系统切流通用方案(流量侧灰度通用套路)互链。与 如何出一个技术方案(方案评审视角)互链。

三条铁律 ​

1)任一时刻只有一个真相源:切写之前旧库为准,切写之后新库为准。双写不是两个真相,是"一主一从影",从影侧的失败只记日志不阻塞业务。 2)每一步都有闸门:上一步验收不通过不进下一步。每一步都保留回退到上一步的能力,直到观察期满主动收口。 3)迁移链路是常驻基础设施:全量是一次性任务,增量同步是常态化链路。要按线上系统标准配监控(延迟/断流/脏数据告警)。不是跑完就扔的脚本。

迁移六步法 ​

text
 ①双写         ②迁移          ③校验         ④灰度读        ⑤切写        ⑥回收
 ─────        ─────         ─────         ─────         ─────        ─────
 写路径同时    存量分批拷贝    条数/抽样/     读流量按      写流量切到    摘双写、
 写旧+新      + binlog 增量   业务三维       1%→10%→      新库(旧库    观察完整
 (旧为准,    追平,同步      核对,         →100% 切      改反向写      业务周期
 新写失败      链路常驻       全绿才放行     到新库,双    兜底,可一     后下线
 只记日志)                  ↓             读比对差异)   键回切)
                            不一致→停在原地修数据
 ═══ 闸门:每步验收不通过不进下一步;每步保留回退到上一步的能力,直到收口 ═══

逐步要点:

  • ①双写:新表/新库写失败降级为日志(本地消息表或 MQ 补偿),绝不能让新库故障拖垮旧库主链路。开关化(配置中心),可秒摘。
  • ②迁移:存量 + 增量两条腿并行(见下节),迁移期间业务照常写旧库。
  • ③校验:三维核对(见下节),是进入灰度前的硬闸门。
  • ④灰度读:按用户尾号/白名单比例把读切到新库,双读比对(读新读旧各一份,以旧为准返回)。比对差异打点,用真实流量验证新库语义,出问题用户无感。
  • ⑤切写:写切到新库,旧库转为反向双写(新为准写旧)。这一步是回滚窗口最窄的时刻,要选低峰、要有自动回切预案。
  • ⑥回收:观察期覆盖一个完整业务周期(至少包含一次月末结算/大促类峰值)。才允许摘反向链路、下线旧库。观察期内的全链路容量验证参考 3+1保障 中的全链路压测。

全量 + 增量与断点续迁 ​

text
 全量迁移(分片断点)
 ┌────────────────────────────────────────────────────┐
 │ 按 PK 分 chunk:(0,100w], (100w,200w] ...            │
 │ 每 chunk 完成后写 checkpoint:chunk_id/max_pk/       │
 │   binlog 位点/状态                                    │
 │ 崩溃重启:跳过 done 分片,从断点 chunk 续跑           │
 │ 幂等写入:INSERT ... ON DUPLICATE KEY UPDATE         │
 │ (重跑安全是断点续迁的前提,写入必须幂等)             │
 └────────────────────────────────────────────────────┘
 增量追平(binlog 位点)
   全量期间业务仍在写旧库 ──► row binlog ──► 解析回放到新库
   消费位点(GTID)随 checkpoint 持久化 ──► 重启从位点续消费
   追平判据:复制延迟 < 1s 持续 10 分钟 且 校验通过
  • 先记位点再拷数据:全量开始前先记录 binlog 位点,增量从该位点消费。否则存量快照与增量流之间有缝隙。
  • 增量回放要保序:同一行的变更按 binlog 顺序应用,多分片消费时按主键哈希分区保行内有序。
  • 同步链路监控三件:延迟(位点差换算成秒)、断流(消费心跳)、错误行数(回放失败进死信人工修)。

数据校验三维 ​

维度做法抓什么问题成本
条数按分片 count(*) 新旧对比(或每行 checksum 聚合后按分片比对)整片丢失/重复/漏回放低
抽样随机主键或哈希抽样 N 万行,全字段逐列比对字段映射错、精度丢失、默认值差异、时区中
业务核对关键对账接口/报表在新旧各跑一遍比对(金额总和、状态分布、当日单数)语义级错误——前两维抓不到的"数据都对但业务不对"高
  • 三维是递进的:条数不一致先修数再谈其他。抽样是统计置信,不是证明。业务核对才是迁移的验收标准(金融场景对账不平,前两维全绿也不能放行)。
  • 校验要在增量暂停窗口内做(短暂停回放、比对、再续),否则边比边写永远对不齐。比对结果差异要能下钻到行级清单。

踩坑高发区(每一条都是真实事故的形状) ​

  • 自增主键冲突:双写两侧各自自增,一旦发生回切,主键区间打架。迁移初始化时就把新库自增起点调到远高于旧库的水位,或全面改用分布式 ID(雪花类)。
  • 双写顺序写死:正向阶段"先旧后新",反向阶段顺序对调。代码里不能写死顺序假设,顺序由配置驱动。旧库写成功、新库写失败要落补偿队列,而不是静默丢弃。
  • 字段精度与默认值漂移:时间精度(秒 vs 毫秒)、decimal 位数、新库列默认值不同、字符集 collation 差异。抽样校验做全字段逐列比对就是为抓它们。
  • 缓存与 DB 的一致性窗口:切写瞬间,旧值还躺在缓存里。切写前主动失效热点 key,或接受短暂脏读。金融场景不可接受,则灰度读期间双读比对加清缓存兜底。
  • 漏迁移"看不见的写入路径":定时任务写状态表、归档任务写历史表、运营后台直改配置表。迁移范围要按变更入口清单(谁在写这张表)核对,而不是只看表清单。
  • 对账口径没对齐:新旧库的"当日订单数"统计口径(时区/状态过滤/软删)不一致。业务核对永远差几百单。对账 SQL 先在双库跑同一份历史数据验证口径,再上线。

回滚窗口设计 ​

阶段真相源回滚动作数据代价窗口长度
①双写旧库摘新库写零(新库可重建重来)天级,可从容
④灰度读旧库读比例回 0零天级
⑤切写初期新库写切回旧 + 反向链路补差秒级窗口内新写需对账补小时级,最窄
⑥回收前新库反向链路=最后回滚位随时间上升周级,必须主动收口
  • 切写窗口三看:错误率、P99 延迟、双读比对差异率,任一越限自动(或一键)回切。回切动作要在演练里真实执行过,不是纸面预案(演练纪律见 故障复盘与OnCall体系)。
  • 窗口必须收口:长期"双写+反向双写"挂着,等于系统永远背着迁移的复杂度。任何一次双写故障都会演变成数据分叉事故。收口日期在方案评审时定死。

大表 DDL:gh-ost 原理 ​

亿级大表直接 ALTER 会长时间锁写。gh-ost(github online schema change)用"影子表 + binlog 回放 + 原子 rename"把锁窗口压到秒级:

text
 原表 t(亿级行)                     影子表 _t_gho(空表,目标结构)
     │ ①全量拷贝:按主键 chunk SELECT→INSERT,速率受控 ▲
     │ ②增量回放:gh-ost 伪装成从库拉 row binlog,     │
     │   把 t 的增删改同步应用到 _t_gho(与①并行)       │
     ▼                                              │
   业务写 t ──row binlog──────────────────────────────┘
     ③收尾 cutover:延迟≈0 且低峰时段 → 秒级锁表 →
       原子 RENAME t ⇄ _t_gho(rename 前可演练、可随时取消放弃)
对比项gh-ostpt-online-schema-change
变更捕获伪装从库读 binlog,异步旁路原表建 AFTER 触发器,写路径内联
侵入性无触发器,与外键/级联/触发器存量冲突小触发器放大每次写,高峰期易拖垮主库
限流按从库延迟/主库负载动态 throttle,可暂停可恢复需自行控制 chunk 间隔
cutover原子 rename,可演练可放弃依赖触发器追尾 + rename
  • 选型一句话:主库写压力大、有从库可读 binlog、要随时可停——gh-ost;存量触发器不可避、无 binlog 权限——pt-osc。
  • gh-ost 迁移单表结构,跨库/分库分表搬迁(数据搬家)用本篇六步法——两者是"改结构"与"搬数据"两类问题。

实践清单 ​

  • [ ] 方案里写明每个阶段的真相源、回滚动作、窗口时长,收口日期评审时定死
  • [ ] 新库写失败降级为日志,绝不阻塞旧库主链路;双写开关配置中心秒级可摘
  • [ ] 存量分 chunk + checkpoint + 幂等写入,任何一步崩溃可断点续跑
  • [ ] 新库自增起点已调高或已切换分布式 ID,防回切主键冲突
  • [ ] 迁移范围按"变更入口清单"核对,定时任务/运营后台这类非主流程写入路径无一遗漏
  • [ ] 增量链路有延迟/断流/死信三类告警,按线上系统标准值守
  • [ ] 校验三维全绿(业务核对含对账)才进灰度;差异可下钻到行级
  • [ ] 灰度读双读比对打点差异率,切写窗口三看指标越限自动回切
  • [ ] 回滚/回切动作在预演环境真实执行过,不是纸面预案
  • [ ] 观察期覆盖完整业务周期后才回收旧库;反向链路下线当天全量对账一次

两问两答 ​

双写阶段为什么要"新写失败只记日志",不一致不是从这里产生的吗?

双写阶段的真相源是旧库,新库只是"影子"。让新库写失败阻塞主链路,等于给还没切流的库赋予了拖垮线上的权力——方向反了。影子侧的失败进补偿队列异步重放,靠增量链路的追平能力兜底。真正的对账闸门在第三步校验,而不是在业务写路径上。原则只有一条:新库的一切问题,都不能让旧库的用户买单。

校验三维全绿了,为什么还必须灰度读?

校验证明的是"迁移时刻的数据一致",灰度读验证的是"新库作为真相源之后的语义一致"。执行计划差异(优化器版本不同)、隔离级别与锁行为差异、字符集排序差异、慢查询超时。这些不产生"数据不同",产生"行为不同"。只有让真实读流量带着真实的参数组合去撞新库。并用双读比对兜底,才能在用户无感的前提下把行为差异暴露完。

一句话总结 ​

双写铺路、增量追平、校验放行、灰度过渡、切写最险、回收收口。迁移工程的安全感不来自"不会出问题",而来自"每一步出问题都知道退到哪里、代价是什么"。这条"同步链路 + 双向复制 + 流量切换"的底座,正是异地多活单元化的技术前提(见 多活容灾与单元化)。

2026-10 新增(工程方法论系列)。