这是我参与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.