数据迁移的一点技术积累-mysql

907 阅读5分钟

这是我参与11月更文挑战的第4天,活动详情查看:2021最后一次更文挑战

不要回答,不要回答,不要回答。

1 前言

由于前段时间的工作与系统重构有关,刚好负责到了数据迁移的部分。数据迁移,就是把旧的系统数据迁移到新的系统中来用以保持系统的连续、持续和稳定性。

2 迁移数据的特点

数据迁移,就是把数据迁移到另外一个数据库中,实现的方式有很多种。最常见的就是使用 dataX 来进行数据同步,但是这种同步数据的方式是基于数据结构没有变化的,但是如果数据结构发生了变化又该怎么处理呢?

在实际的操作过程中遇到的问题就是这样的,数据需要清洗。比如账户数据,由于历史的原因,需要对账户重新设置Id,源数据是没有分库分表的,在新数据库中是需要分库分表的,而且在用户账户变动流水中,还要重新根据新的用户Id来给流水中的用户信息进行赋值。这就增加了迁移工作的复杂性,再加上迁移数据本身需要建立源数据和目标数据的映射关系,不同字段值还要做转换。

graph LR
A[源数据] --> B(数据清洗) -->  C[目标数据]  

以用户信息表为例,我们可以看到不仅需要做映射关系,还需要做字段间的转换。

# 源数据账户信息表
CREATE TABLE `t_source_account_info` (
  `user_no` varchar(20) NOT NULL COMMENT '用户号',
  `status` int NOT NULL DEFAULT '1' COMMENT '状态 1-禁用 0-启用',
  `version` int(11) NOT NULL DEFAULT '0' COMMENT '版本号',
  `create_time` timestamp NULL COMMENT '创建时间',
  `modify_time` timestamp NULL COMMENT '更新时间',
  PRIMARY KEY (`user_no`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin comment "源数据账户表";

# 源数据账户信息表
CREATE TABLE `t_target_account_info` (
  `id` bigint(20) NOT NULL COMMENT '主键id',
  `user_no` varchar(20) NOT NULL COMMENT '账户编号',
  `source` varchar(32) NOT NULL COMMENT '来源',
  `status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '状态 0-禁用 1-启用',
  `creator` varchar(32) CHARACTER SET utf8 NOT NULL COMMENT '创建者',
  `create_time` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `modify_time` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin comment "目标数据账户表";

针对需要迁移的数据,这边也做了单独的分析,主要分为账户数据和流水数据。

# 以下是我们针对数据的性质做的定性分析
1 账户数据在保存后只有修改的情况。 
2 流水数据在新增后不会修改,只有新增的情况。
3 数据是不允许删除的。

3 数据迁移的技术路线

针对数据的特点,我们做出了如下的数据技术路线。

graph LR
A[源数据] -->C(数据类型)
    C -->|账户数据| D[变动更新的方式] --> G[目标库账户表]
    C -->|流水数据| E[增量更新的方式] --> H[目标库流水表]

考虑到原数据表中的更新时间没有在数据变动时最做记录,我们修改了字段,让数据发生变化时,modify_time 的时间更新为当前时间,这样我们在做数据处理时,只需要查询最近有变化的数据即可,缩小的数据迁移的范围,节省账户迁移的时间。变更语句如下:

# 这样既可以在数据发生变化时进行记录修改时间
ALTER TABLE `t_source_account_info` MODIFY COLUMN `modify_time` datetime NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP  COMMENT '修改时间';

针对账户流水数据,我们采用增量更新,在更新时需要查询上一次迁移的最大id,作为本次迁移的起始id即可。

最终我们的实现查询是这样的:

# 账户迁移查询条件
select * from t_source_account_info where modify_time >= "上次更新最大时间"
# 流水迁移查询条件
select * from t_account_log where id > "上次迁移的最大id"

至于后续的迁移代码,就是按照事先梳理的映射关系文档进行coding,完全就是一项体力活了。

4 总结和感想

在这个迁移数据的过程中,最大的收获就是分析数据的特点,使用不同方式进行操作,体现了分而治之的思想。做事情是比较简单的,在做之前要想好方法,才能事半功倍。 在这个迁移的工作中,就是对数据的新增和插入,用到了雪藏很久的 mysql 特性:

# 1 insert ignore 这个是在插入时如果存在数据则忽略插入,不存在则进行插入,在流水迁移时特别方便,可以支持数据的重复迁移,而不用担心插入冲突,很好的实现了幂等性。
insert ignore t_table_name (`id`,`name`) values ('1','小明');
# 2 ON DUPLICATE KEY UPDATE 这个在插入和更新的时候都可以使用,如果不存在则插入,存在则更新
insert ignore t_table_name (`id`,`name`) values ('1','小明') on duplicate key update id = 1 ,`name` = '小明';
# 3 gourp_concat 函数 这个会对结果进行分组,将组内需要拼接的数据使用 , 进行拼接
# 如下所示查询的结果就是 1  2,3,4 用户1 所有的流水id 为 2,3,4
select user_id, group_concat(id) from t_account_log group by user_id;

新发现了数据库连接的配置参数useAffectedRows

useAffectedRows=true,默认为false。是否使用影响行数,默认为找的所有修改行数,即false。
在update 的情况下,更新3条,如果只有两条有变化,则返回2(useAffectedRows=true),否则返回3.