十几亿数据迁移的断点续传 十几亿数据做全量迁移中间不可能一路跑到结束。迁移过程中总会遇到各种问题迁移程序有Bug要修、或者需要重启又或者数据库压力太大要暂停等都有可能。如果每次出了问题都从第一条重新开始这个迁移基本就做不下去了。所以断点续传是必须的。我们的做法并不复杂。建一张迁移进度表只记录当前已经扫描到的最大主键ID。每处理完一批数据就更新一次这个ID。程序启动时先读取这张表里的last_id然后继续往后扫描。这样无论程序重启多少次都能从上一次成功的位置继续跑不需要重新开始也不用人工指定起始位置。迁移刚开始那几天经常是一边跑一边修Bug这套机制帮我们省了不少时间。修完代码重新部署程序自己就接着往下迁移了。这里还有一个好处。整个迁移程序是无状态的真正记录迁移进度的是数据库里的那张进度表而不是程序内存。程序什么时候挂、部署多少实例都不会影响迁移进度。数据扫描采用按主键范围递增的方式。SQL其实挺简单的就是查询id lastId的数据按主键升序取2000条。没有使用OFFSET分页。十几亿数据如果用 OFFSET越往后翻越慢因为MySQL需要先跳过前面所有记录。按主键范围扫描始终走聚簇索引无论扫描到第几亿条查询性能基本都比较稳定。这种写法还有一个优点就是不要求ID连续。生产环境跑了很多年的业务表中间通常都会有大量ID空洞。有的是物理删除有的是历史数据清理留下来的。idlastId 并不关心中间有没有空洞的只会继续扫描下一批真实存在的数据。如果按BETWEEN去切区间这些空洞会导致不少批次查出来都是空结果相当于白跑了一次。迁移的时候每批处理2000条批次之间休眠100毫秒。这些参数没有写死而是放到Nacos统一管理。如果发现写入服务压力比较大就把批次调小一点、休眠时间调长一点如果机器资源比较充足就适当提高批大小加快迁移速度。整个过程不用改代码也不用重启服务配置推送后下一批数据就会生效。另外迁移过程中失败记录一定要单独保存。RPC超时、历史脏数据、字段转换异常这些问题都很常见不可能保证一次全部成功。我们专门建了一张迁移失败日志表记录来源表、来源主键、错误类型、错误信息以及原始数据的JSON快照。这样排查问题就很快。如果某一种错误突然大量增加很容易就能定位到底是写入服务出了问题还是迁移逻辑遗漏了某个场景。问题修复以后只需要针对失败记录重新执行一次重试不需要把整个迁移任务重新跑一遍。整个迁移系统真正依赖的其实只有两张表。一张记录迁移进度一张记录迁移失败数据。前者保证程序随时可以恢复后者保证任何异常都有迹可循。对于十几亿数据的迁移来说建议都建立这两张表。