数据迁移中数据一致性的思考

733 阅读4分钟

一个数据迁移的过程大体入下所示,主要分为以下几个步骤

shuju.png

  • 准备目标表

  • 初始化目标表的数据

    • 可以使用 mysqldump
    • 为了加快导入速度,可以开启 extend-insert 选项,将多行合并为一个 insert 语句
      • 为什么合并为一个可以加快速度呢?
        1. 减少 SQL 语句的数量,减少了数据库在处理解决和执行的 SQL 语句数量
        2. 减少网络往返次数
        3. 优化事务处理,一个事务可以处理更多的数据
        4. 减少日志写入次数,降低I/O操作频率,从而提高了性能
      • 其他优化项:
        • 关闭唯一性检查和外键检查,源表已经保证了这两项
        • 关闭 binlog,导入数据用不着 binlog
        • 调整 redo log 的刷盘时间,将 inndb_flush_log_at_trx_commit 调整为 0,这样子 InnoDB 会将日志缓冲区的内容每秒写入日志文件,而不是在每次事务提交时都进行写入和刷新
  • 第一次校验与修复,以源表为准,比如表中有 updated_time 那么可以根据这个字段来进行修复

  • 业务开启双写,以源表为准,写入目标表失败可以忽略,因为有校验与修复脚本,并且插入的主键跟源表的保持相同

  • 增量校验和数据修复

    • 一边保持双写,一边校验最新修改的数据,有两个方案,一个是前面说的,以 updated_time 这个列;第二个是利用 binlog
    • 业务注意要是软删除,因为如果是硬删除,在删除源表成功且删除目标表失败的情况,用源表的数据去修复时,是找不到这条数据的,这时候就要用反向校验,如果目标表有,源表没有,就删除掉目标表的数据
    • 使用 binlog 方案的话,要注意主从延迟的问题,因为监听 binlog 时,有可能接受的数据是旧的,我们还得去主库的表进行对比,如果跟主库的表一致,才进行修复数据,如果都查主库,那也有可能造成主库压力过大,那么可以使用双重校验逻辑,先查从库的数据,不一致的话再去查主库
  • 切换双写顺序

    • 如果直接切换到目标表单写,这样子如果出现问题没法回滚,所以我们切换时的有个过度阶段,先写目标表,再写源表,如果数据迁移出现了问题,那么可以回滚为先写源表,再写目标表
  • 保持增量校验与修复

    • 这时候以目标表为准
  • 切换成读写到单目标表

这里也就是一个数据迁移过程的大体流程了,但是这也引发了我一个思考,就是如果 切换双写 顺序后,出问题了,想要进行回滚,这时候是不是会有数据不一致的问题?

刚开始双写顺序为 先写源表 -> 再学目标表(新表),这时候如果写目标表失败了,可以直接忽略,因为有个脚本会进行数据校验和修复,后面切换双写顺序,先写目标表,再写源表,这时候如果不忽略,且是同个库,那么可以用数据库的事务进行保证,要两个都写成功才提交,如果不是同个库,写目标表成功了,写源表失败了,也可以通过数据校验脚本(这时候脚本的数据就要以目标表为准了),但是如果这时出错了,想切换回原来的双写顺序,就可能源表没有数据,但是目标表有(这时候数据校验和修复的脚本也切换回以源表为准了)就会出问题了。

如果要保证完全一致性,我构想了几种方案:

  • 使用分布式事务
  • 写入源表失败进行重试,重试一定次数还未成功,进行熔断,切换回原来的双写顺序
  • 保持最终一致性就好(数据不允许删除),比如 A 表 比 B 表多数据,那么将 A 表的多出来的数据迁移到 B 表,反过来也是如此