如何实现数据库的不停服迁移?
- 标签:后端、MySQL、数据库、场景题
🏛 业务架构师
不停服迁移数据库(或迁移到新表/新库)是高危操作,我的方案核心是双写 + 存量迁移 + 校验 + 灰度切流 + 可回滚五步,每步都可中止。原则:迁移的可靠性优先于速度,任何一步出问题都能退回。
标准流程
1. 双写(增量先通) 业务代码同时写新旧库(或用 binlog 同步工具替代双写代码),以旧库为准,新库写失败只记日志不阻断业务——新库坏不影响交易。
2. 存量迁移 后台任务分批(按主键游标)把历史数据搬到新库,限速执行避免影响线上;迁移期间的双写增量保证不丢。
3. 数据校验(最关键的一步) 全量或抽样的 count/sum/checksum 比对,业务关键字段逐条核对。没有校验的迁移等于裸奔——我要求校验报告作为切流的前置条件,差异必须清零或说明原因。
4. 灰度切读 按百分比把读流量切到新库(1% → 10% → 50%),观察 RT、错误率、数据一致性;写仍双写。灰度期间任何异常一键切回旧库,用户无感。
5. 切写与收尾 写切到新库(保留双写一段时间),观察稳定后停旧库写入,旧库保留 N 天作为兜底回滚手段,最后归档下线。
业务架构师的三个补充
- 窗口选择:大版本迁移选业务低峰周(避开大促、月初结算),给回滚留足时间
- 资损表加对账:交易类数据迁移期间每日对账加倍,宁可多花机器也不能错一笔
- 用工具不手写:gh-ost/pt-osc 处理表结构变更、DTS 类产品处理跨库迁移,自写迁移程序的 bug 成本远高于工具授权费
- 演练:生产迁移前在预发环境完整走一遍并计时,切流时间表精确到分钟
🏗 系统架构师
不停服迁移我做过生产库几百 GB 的 MySQL 搬迁,核心公式是:全量 + 增量 + 校验 + 原子切换。
1. 全量迁移(可重复执行) 用数据同步工具(DataX、mysqldump --single-transaction 或 MyDumper 并行导)把存量灌入新库。要点:低峰执行、分批并发、先导数据后建二级索引,不锁源库业务。全程对线上只读不写,可以随时重跑。
2. 增量追平(关键步骤) 开启 binlog(ROW 格式),用 Canal/Debezium 把全量开始时间点之后的变更持续应用到新库。这里必须注意位点一致性:全量导出用一个一致性快照(--single-transaction 或导出前记 show master status 的 binlog 位点),增量从这个位点消费,保证不丢不重;ROW 格式 + 幂等应用(按主键 upsert)解决重复消费。
3. 数据校验 增量追到延迟接近 0 后,全量 checksum 比对(pt-table-checksum 或分块 CRC 抽样),不一致块修复。校验不过绝不敢切。
4. 原子切换
- 短写入暂停(秒级):挂维护页或网关层禁写,等增量消费完最终位点
- 切换方式按代价选:改数据库连接配置中心热更并重启生效最快;讲究的做 DNS/Proxy(如 ProxySQL/DBProxy)切流量,应用无感
- 切完旧库保留只读一周以上,随时可回滚
风险点:自增 ID 要预留跳跃空间避免切换后主键冲突;双写方案我不推荐作为主路径——侵入大、两边不一致的排查成本远高于 binlog 方案。
💻 全栈开发专家
我做过两次库迁移,套路是存量同步 + 增量双写 + 校验 + 灰度切流,全程可回滚。
步骤
存量搬迁:老库按主键游标分批导出到新库(或 DTS/DataX),期间业务照常写老库。
增量追平:开启 binlog 同步(Canal / 云厂商 DTS)把存量期间的变更实时打到新库,追上实时位点。
双写切换写路径:应用改成"老库为主、新库异步补写",双写不一致以老库为准,新库靠对账修复:
java
oldMapper.insert(order); // 主
mq.send(orderChangeMsg(order)); // 异步补新库,失败落补偿表数据校验:count 总量 + 分块 checksum(按 id 段聚合
CRC32比对)+ 金额汇总核对,差异清单自动修复重跑。灰度切读:按用户尾号 1% → 10% → 50% 把读流量切到新库,配置中心开关随时回切;观察一周无差异再切写,最后停双写下线老库。
关键坑
- 双写阶段禁止双向写:切换期写路径唯一(写老库),新库永远从属。
- 自增主键在新库要设置起始值避开区间,或者干脆改用雪花 ID。
- 切读前必须把新库的连接池预热和慢查询治理做完,否则一开流量就抖动告警。
- 全程配对账 Job(每天 diff 昨日数据),这是敢切 100% 流量的底气。