一个数据迁移的过程大体入下所示,主要分为以下几个步骤
-
准备目标表
-
初始化目标表的数据
- 可以使用 mysqldump
- 为了加快导入速度,可以开启 extend-insert 选项,将多行合并为一个 insert 语句
- 为什么合并为一个可以加快速度呢?
- 减少 SQL 语句的数量,减少了数据库在处理解决和执行的 SQL 语句数量
- 减少网络往返次数
- 优化事务处理,一个事务可以处理更多的数据
- 减少日志写入次数,降低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 表,反过来也是如此