数据迁移实战
问题
需要将 MySQL 数据迁移到新库(分库分表、更换存储、跨机房),如何做到不停机、数据不丢?
答案
迁移方案对比
| 方案 | 停机时间 | 风险 | 适用场景 |
|---|---|---|---|
| 停机迁移 | 长(小时级) | 低 | 数据量小、允许停服 |
| 双写迁移 | 无 | 中 | 实时性要求高 |
| Binlog 增量同步 | 秒级 | 低 | 大数据量、推荐 |
推荐方案:全量 + 增量 + 对账
第一步:记录 Binlog 位点
-- 记录当前位点,后续从这里开始增量同步
SHOW MASTER STATUS;
-- +------------------+----------+
-- | File | Position |
-- +------------------+----------+
-- | mysql-bin.000042 | 15432 |
第二步:全量导入
全量数据导出导入
# 小表直接 mysqldump
mysqldump -u root -p --single-transaction old_db orders > orders.sql
mysql -u root -p new_db < orders.sql
# 大表分批导出(减少锁和内存压力)
# 用程序分页 SELECT + 批量 INSERT
分批迁移大表
int lastId = 0;
int batchSize = 5000;
while (true) {
List<Order> batch = oldDb.query(
"SELECT * FROM orders WHERE id > ? ORDER BY id LIMIT ?", lastId, batchSize);
if (batch.isEmpty()) break;
newDb.batchInsert(batch);
lastId = batch.get(batch.size() - 1).getId();
log.info("已迁移到 id={}", lastId);
}
第三步:增量同步
使用 Canal 监听从记录的 Binlog 位点开始的变更,同步到新库。参考 数据同步方案。
第四步:对账校验
数据校验
public void verify() {
// 1. 数量校验
long oldCount = oldDb.count("SELECT COUNT(*) FROM orders");
long newCount = newDb.count("SELECT COUNT(*) FROM orders");
assert oldCount == newCount;
// 2. 抽样校验(大表不可能全量比对)
List<Long> sampleIds = oldDb.query(
"SELECT id FROM orders ORDER BY RAND() LIMIT 1000");
for (Long id : sampleIds) {
Order old = oldDb.findById(id);
Order newOrder = newDb.findById(id);
assert old.equals(newOrder) : "数据不一致: id=" + id;
}
// 3. 增量校验:对比最近 10 分钟的数据
// 确保增量同步延迟在可接受范围内
}
第五步:流量切换
灰度切换
// 通过配置中心控制读写流量
@Value("${migration.read-new-db-percent:0}")
private int readNewDbPercent;
public Order queryOrder(Long id) {
// 灰度切换:逐步将 10% → 50% → 100% 读流量切到新库
if (ThreadLocalRandom.current().nextInt(100) < readNewDbPercent) {
return newDb.findById(id);
}
return oldDb.findById(id);
}
切换步骤:
- 读流量切 10% 到新库 → 验证
- 读流量切 100% 到新库
- 写流量切到新库(关键!需确保增量同步已完全追上)
- 观察一段时间后下线旧库
双写方案
适合实时性要求高但数据量不大的场景:
双写 + 异步校验
@Transactional
public void createOrder(Order order) {
// 写旧库(主)
oldDb.insert(order);
try {
// 异步写新库(不影响主流程)
asyncExecutor.execute(() -> newDb.insert(order));
} catch (Exception e) {
log.error("双写新库失败", e);
// 记录失败,后续补偿
}
}
双写的风险
双写无法保证两个库的事务一致性。如果旧库成功新库失败,需要补偿机制修复。生产中更推荐 Binlog CDC 方案。
常见面试问题
Q1: 不停机迁移关键要保证什么?
答案:
- 数据完整性:全量 + 增量不能丢数据
- 数据一致性:迁移后新旧库数据一致
- 可回滚:切换后发现问题可以切回旧库
- 业务无感:用户无感知,无停机
Q2: 分库分表迁移有什么特殊问题?
答案:
- 数据按分片键路由到不同库表
- 全量导入时需要按新的分片规则写入
- 跨库查询需要改造(聚合查询、排序分页)
- 工具推荐:ShardingSphere-Scaling
详见 分库分表。
Q3: 如何确保切换时数据不丢?
答案:
- 增量同步延迟 < 1s(监控确认)
- 切写前暂停几秒确保增量追平
- 切换后旧库 Binlog 继续消费一段时间确认无新数据
- 全量对账通过后才下线旧库