1. 引言
数据迁移是一个常见的需求,比如以下的场景,我们都需要进行数据迁移。
- 大表修改表结构
- 单表拆分进行分库分表、扩容
- 系统重构,使用新的表结构来存储数据
2. 迁移准备
2.1 备份工具
2.1.1 mysqldump
mysqldump
是 MySQL 自带的用于备份和恢复的命令行工具。它允许用户导出 MySQL 数据库的结构、数据以及表之间的关系,以便在数据库发生问题时进行恢复。它是一个逻辑备份工具,导出的内容是一条条 SQL。
mysqldump 使用介绍
2.1.2 XtraBackup
它使用了 InnoDB 存储引擎的数据备份技术,支持增量备份和恢复,并且支持多主机备份和恢复。它是一个物理备份工具,相当于直接复制 InnoDB 的底层存储文件。
2.2 innodb_autoinc_lock_mode
innodb_autoinc_lock_mode
是 InnoDB 引擎里面控制自增主键生成策略的参数,它有三个取值。
- 0:使用表自增锁,在 INSERT 语句结束之后才会释放锁;
- 1:使用表自增锁,如果是普通的 INSERT INTO VALUE 或者 INSERT INTO VALUES 语句,申请了主键就释放锁,而不是整个 INSERT 语句执行完毕才释放。如果是 INSERT SELECT 等语句,因为无法确定究竟要插入多少行,所以都是整个 INSERT 语句执行完毕才释放。
- 2:使用表自增锁,所有的语句都是申请了主键就立刻释放。
在执行 INSERT INTO VALUES 的时候,不管插入多少行,都只申请一次主键,一次申请够,这些主键必然是连续的,我们可以从第一个 ID 推测出全部 ID。
CREATE TABLE `kkk` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`name` varchar(10) NOT NULL COMMENT 'name',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='测试自增主键';
插入两条数据
insert into kkk(name) values('yyh'),('yqq');
查询本次插入的ID值
SELECT LAST_INSERT_ID();
+------------------+
| LAST_INSERT_ID() |
+------------------+
| 1 |
+------------------+
根据返回的结果并结合插入的条数,就可以得到全部的 ID 了。
3. 迁移方案
在真正开始数据迁移前,我们需要考虑以下几个问题
innodb_autoinc_lock_mode
的值,它会影响主键生成策略,在数据迁移时需要考虑处理主键问题;binlog
的模式,在增量校验和修复数据里面使用的是行模式的 binlog;- 数据库规范,比如必须要有更新时间戳,只能逻辑删除,不能物理删除;
- 怎么去实现双写?是否需要业务代码侵入。
3.1 新建目标表,并初始化数据
目标表创建后,首先就是初始化数据。
通过以下两种方式,我们可以去获取原表数据。
- 历史备份
- 导出数据
比如,我们使用 mysqldump
工具导出原表数据,当数据量特别大的情况下,导出和导入的速度都比较慢,可以进行优化。
在导出时
- 开启
extended-insert
选项,进行SQL语句合并;
在导入时
- 关闭唯一性检查和外键检查,原表已经保证了这两项,所以目标表并不需要检查;
- 关闭
binlog
,毕竟导入数据用不着 binlog; - 调整
redo log
的刷盘时机,把innodb_flush_log_at_trx_commit
设置为 0。
3.2 首次数据校验与修复
如果在初始化目标表数据时,使用的是历史备份,此时备份数据是落后于生产数据的;
比如昨天的备份,那么今天的修改目标表就没有;
如果使用的是导出数据,那么在导出数据到导入数据这段时间,原表变化的数据,目标表是没有的。
如果表里有 update_time字段,那么在校验修复时,就可以采用增量方案。筛选出 update_time 大于 导出数据的时间点的数据,然后用原表数据覆盖目标表数据。
比如在某个时间点【T1】停止了这一步骤,在【T1】和开启双写【T2】的这一时间段的数据修改就会丢失。
因为我们后面还会持续进行增量数据校验,可以设置增量校验的起始时间从【T1】之后开始进行。
3.3 业务开启双写
比如我们采用不侵入业务代码方案,利用 AOP 切面,去捕获增删改的调用,篡改为双写模式。
同时,要保证在双写时可以在运行期随时切换状态,比如单写原表、单写目标表、先写原表、先写目标表。
可以利用一个标志位,通过配置中心或接口去修改它的值。
在双写时,有两个问题需要重点关注下,数据一致性问题和主键问题。
3.3.1 数据一致性问题
如果在双写过程中,写入原表成功,写入目标表失败了,该怎么办?
- 忽略不管,因为我们有数据校验和修复机制;
3.3.2 主键问题
在双写的时候,我们需要保证原表数据和目标表数据主键一致,也就是在写入原表时拿到自增主键,然后写入目标表的时候设置好主键。
为什么需要手动设置呢?因为我们保证不了在并发情况下,目标表自增的主键和原表自增的主键是同一个值。
3.4 增量校验和数据修复
增量校验就是一边保持双写,一边检验最新修改的数据,如果不一致,就修复。
比如我们利用更新时间戳update_time
列,定时去查询表数据,找出最近更新过的记录,然后再去目标表里面找到对应的数据,如果两者不相等,就用源表的数据去修复目标表的数据。
这个方案有两个条件:所有的表都是有更新时间戳的,并且是逻辑删除的。
为什么是逻辑删除?
可以思考一下,比如有条数据 id=3,原表删除成功,目标表删除失败,如果是物理删除的话,在原表是找不到的,既然找不到则就比对不了,目标表的那条数据就会一直存在。
这种情况下,我们还可以进行反向全量校验,先去目标表中查找数据,再去原表比对,如果原表不存在,则目标表就可以删除数据了。
3.5 切换双写顺序
通过先写目标表且业务读目标表,再写源表这种方式,万一发现数据迁移出现了问题,还可以回滚为先写源表,再写目标表,确保业务没有问题。
如果直接切换为读写目标表,不写原表的话,万一出现问题,就回滚不了了,因为原表已经丢失数据了。
3.6 保持增量校验与修复
在切换了双写顺序之后,还要继续保持增量校验和修复,注意前面的校验和修复都是以源表为准,在这一步,要以目标表为准。
3.7 断开原表的写入
经过几轮的数据校验与恢复后,就可以断开原表的写入了。