凌晨两点,运维老张盯着屏幕上跳动的迁移进度条,手心全是汗。第37次全量导出,第12次增量追平,他掐着表等一波binlog同步完成。这种场景,但凡做过数据库迁移的人都懂——白天业务不能停,晚上窗口就那么几小时,数据量还动辄几个T,稍有不慎就是丢数据、回滚、背锅三连。干了十年DBA,我踩过的坑比写过的SQL还多,今天把最稳妥的那套方案拆开揉碎讲给你听,就三步,每一步都卡着“零丢失”这个死线来设计。

第一步,先把“搬家”和“住人”分开。很多人上来就全量导出,然后导入,再然后增量同步,听起来顺理成章,但实际操作里,全量导入那几小时,源库还在不停写,你导出去的那份快照早就过期了。正确做法是,先停写,或者至少停掉核心业务写操作,用mysqldump或者pt-archiver这类工具做一次完整快照导出。注意,这里要选对工具,mysqldump适合中小表,单表超过几十G就别硬扛了,换mydumper并行导出,速度能快五倍以上。导出完先别急着导目标库,把这份快照丢到目标环境里,做一次校验,行数、checksum、关键字段的min/max值,全对上了再进行下一步。这一步的核心价值是建立一个“基线”,后面所有增量都基于这个基线来算,基线歪了,后面全白搭。
第二步,增量追赶,这里有个细节很多人忽略。全量导入完成后,源库可能又累积了几十分钟甚至几小时的写操作,这时候需要开启binlog解析或者使用DTS、DataX这类工具做增量同步。但你要明白,增量同步不是无限追的,它必须基于第一步那个基线的时间点或位点来拉取。我见过最蠢的做法是,全量导完才想起来没记录binlog文件名和pos,结果增量对不上,只能全量重来。所以第一步导出时,务必记录下当时的binlog坐标或者GTID,这个信息要写在迁移文档里,贴到明显位置。增量追平后,别急着切换,先让增量任务跑个十几分钟,观察源库和目标库的数据延迟是否稳定在秒级以内,同时对比两张表的count值和关键业务表的更新时间戳,确认没有持续的乱序写入。
第三步,切换前的“一道保险”——影子验证。这一步是零丢失的关键,也是最容易被人跳过的。具体做法是,在目标库上开启一个只读副本,或者干脆用逻辑复制把目标库的最新状态再同步到一个临时库,然后让测试人员或者自动化脚本,拿线上的真实查询流量打一遍影子库,重点看两类问题:一是数据缺失,比如某张关联表在目标库上外键对不上;二是数据漂移,比如decimal精度变了、时区差了几小时、字符集排序规则不一致。这些坑,我全踩过——有一次就是因为源库是utf8mb4generalci,目标库建表时默认成了utf8mb4unicodeci,排序规则不同,导致某个分页查询结果错乱,业务方差点炸了。影子验证跑通后,再执行真正的切换操作:停源库写操作,等增量延迟归零,确认binlog位点一致,然后改应用连接串,指向目标库,整个过程控制在几十秒内。
这里有个认知误区得纠正一下:很多人以为零丢失就是“丢一条都不行”,其实严谨的数据库迁移里,零丢失指的是“在声明的时间点之后,不丢任何已提交事务”。也就是说,你得跟业务方明确,切换窗口内那几十秒的写操作是暂停的,或者由应用层缓冲重试,这不算丢失,这是切换的固有成本。真正要防的是切换完成后,源库还有未同步完的事务,或者目标库上出现了重复写入。所以切换后至少观察24小时,每日跑一次数据一致性校验,用pt-table-checksum这类工具对比源库和目标库的每张表,发现有差异就立刻回滚,别抱侥幸心理。
再说个实操小技巧:大表迁移别一把梭,拆分成多个并行任务。比如一张十亿行的订单表,按id范围切成100个分片,每个分片一个线程去导,速度能提升一个数量级。但注意,分片切分要基于主键或者唯一索引,别用普通字段,否则会有数据倾斜,有的分片几秒跑完,有的跑一小时。另外,目标库的存储引擎、行格式、压缩策略,最好在迁移前就统一规划好,别到了导入时才临时改,容易引发锁表或者空间不足的幺蛾子。
说回运维老张那个场景。他后来用了这套三步法,全量导出花了4小时,增量追平用了20分钟,影子验证跑了半小时,切换窗口控制在45秒内,业务方几乎无感知。第二天一早,他收到一条告警:一致性校验通过,差异记录数为0。那一刻他才松了口气,说以前迁移像走钢丝,现在终于觉得手里有根安全绳了。其实数据库迁移这事,说到底就是“敬畏数据,尊重流程”。你少一步,可能侥幸没事,但运气不会每次都站在你这边。把每一步都做扎实,零丢失就不是玄学,而是工程纪律。下次再有人问你迁移怎么搞,你就把这篇文章甩给他,然后加一句:按这个来,别自己发明轮子。


