专栏名称: 程序员鱼皮
鹅厂全栈开发,持续分享编程技法和实用项目
目录
相关文章推荐
中国兵器工业集团  ·  人才院2项案例入选“国有企业管理实践案例库” ·  2 天前  
中国兵器工业集团  ·  锚定目标拓市场丨奋战“开门红”⑥ ·  3 天前  
51好读  ›  专栏  ›  程序员鱼皮

给你10亿数据,如何做迁移?

程序员鱼皮  · 公众号  ·  · 2025-03-10 13:31

正文


前言

某次金融系统迁移项目中,原计划8小时完成的用户数据同步迟迟未能完成。

24小时后监控警报显示:由于全表扫描 SELECT * FROM users 导致源库CPU几乎熔毁,业务系统被迫停机8小时。

这让我深刻领悟到—— 10亿条数据不能用蛮力搬运,得用巧劲儿递接

今天这篇文章,跟大家一起聊聊10亿条数据,如何做迁移,希望对你会有所帮助。

一、分而治之

若把数据迁移比作吃蛋糕,没人能一口吞下整个十层蛋糕;

必须切成小块细嚼慢咽。

避坑案例:线程池滥用引发的血案

某团队用100个线程并发插入新库,结果目标库死锁频发。

最后发现是主键冲突导致—— 批处理必须兼顾顺序和扰动

分页迁移模板代码

long maxId = 0;  
int batchSize = 1000;  
while (true) {  
    List users = jdbcTemplate.query(  
        "SELECT * FROM users WHERE id > ? ORDER BY id LIMIT ?",  
        new BeanPropertyRowMapper<>(User.class),  
        maxIdbatchSize  
    )
;  
    if (users.isEmpty()) {
        break;  
    }
    // 批量插入新库(注意关闭自动提交)  
    jdbcTemplate.batchUpdate(  
        "INSERT INTO new_users VALUES (?,?,?)",  
        users.stream().map(u -> new Object[]{u.id, u.name, u.email}).collect(Collectors.toList())  
    );  
    
    maxId = users.get(users.size()-1).getId();  
}

避坑指南

  • 每批取递增ID而不是 OFFSET ,避免越往后扫描越慢
  • 批处理大小根据目标库写入能力动态调整(500-5000条/批)

二、双写

经典方案是停机迁移,但对10亿数据来说停机成本难以承受,双写方案才是王道。

双写的三种段位:

  1. 青铜级 :先停写旧库→导数据→开新库 →风险:停机时间不可控
  2. 黄金级 :同步双写+全量迁移→差异对比→切流 →优点:数据零丢失
  3. 王者级 :逆向同步兜底(新库→旧库回写),应对切流后异常场景

当然双写分为:

  • 同步双写
  • 异步双写

同步双写实时性更好,但性能较差。

异步双写实时性差,但性能更好。

我们这里考虑使用异步双写。

异步双写架构如图所示:

代码实现核心逻辑

  1. 开启双写开关
@Transactional  
public void createUser(User user) {  
    // 旧库主写  
    oldUserRepo.save(user);  
    // 异步写新库(允许延迟)  
    executor.submit(() -> {  
        try {  
            newUserRepo.save(user);  
        } catch (Exception e) {  
            log.error("新库写入失败:{}", user.getId());  
            retryQueue.add(user);  
        }  
    });  
}
  1. 差异定时校验
// 每天凌晨校验差异数据  
@Scheduled(cron = "0 0 3 * * ?")  
public void checkDiff() {  
    long maxOldId = oldUserRepo.findMaxId();  
    long maxNewId = newUserRepo.findMaxId();  
    if (maxOldId != maxNewId) {  
        log.warn("数据主键最大不一致,旧库{} vs 新库{}", maxOldId, maxNewId);  
        repairService.fixData();  
    }  
}

三、用好工具

不同场景需匹配不同的工具链,好比搬家时家具用货车,细软用包裹。

工具选型对照表

工具名称
适用场景
10亿数据速度参考
mysqldump
小型表全量导出
不建议(可能天级)
MySQL Shell
InnoDB并行导出
约2-4小时
DataX
多源异构迁移
依赖资源配置
Spark
跨集群大数据量ETL
30分钟-2小时

Spark迁移核心代码片段

val jdbcDF = spark.read  
    .format("jdbc")  
    .option("url", "jdbc:mysql://source:3306/db")  
    .option("dbtable", "users")  
    .option("partitionColumn", "id")  






请到「今天看啥」查看全文