Otter 同步出现时间列上值不一致问题
背景:
由于历史原因我们DB中引入了多源复制技术,在之后的使用过程中发现了一些弊端,于是准备去掉这套架构,使用otter来代替多源复制。在otter同步跑了很长一段时间之后,我们准备上线这套集群,却发现了一些不一致的情况。
问题:
之前otter使用的不是特别多,对其稳定性也不是很有信心,为了检验源库数据和目的数据的一致性,避免切换后出现数据不一致的问题,我们写脚本抽样比对了部分数据,结果发现两边的确存在数据不一致的情况,以下是其中一个案例。
源数据
< 2357 999999999 0 刘 2030420 罗 2409 红娘二部(易) 270 重庆解放碑店 2012218 陈 2020-05-18 02:27:04 2020-05-19 12:20:18 NULL 0 0 0 0 2020-05-19 12:20:18 2020-05-19 12:20:18
目的数据
> 2357 999999999 0 刘 2030420 罗 2409 红娘二部(易) 270 重庆解放碑店 2012218 陈 2020-05-18 02:27:04 2020-05-19 12:20:18 NULL 0 0 0 0 2020-05-19 12:20:18 2020-05-27 10:53:37
表结构
CREATE TABLE `ArchiveMeetDetail` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', `memberId` int(11) NOT NULL , `sex` tinyint(3) NOT NULL , `trueName` varchar(10) NOT NULL , `belongWorkerId` int(11) NOT NULL , `belongWorkerName` varchar(20) NOT NULL , `belongGroupId` int(11) NOT NULL , `belongGroupName` varchar(20) NOT NULL , `belongDeptId` int(11) NOT NULL , `belongDeptName` varchar(20) NOT NULL , `workerId` int(11) NOT NULL , `workerName` varchar(255) NOT NULL , `archiveTime` datetime NOT NULL , `archivePassTime` datetime NOT NULL , `firstMeetTime` datetime DEFAULT NULL , `meetThisMonth` int(11) NOT NULL DEFAULT '0' , `miniMeetThisMonth` int(11) NOT NULL DEFAULT '0' , `meetTotal` int(11) NOT NULL DEFAULT '0' , `miniMeetTotal` int(11) NOT NULL DEFAULT '0' , `createTime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP , `updateTime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP , PRIMARY KEY (`id`), KEY `idx_member_id` (`memberId`) USING BTREE, KEY `idx_group_id` (`belongGroupId`) USING BTREE, KEY `idx_dept_id` (`belongDeptId`) USING BTREE, KEY `idx_archive_pass_time` (`archivePassTime`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=3543 DEFAULT CHARSET=utf8
我们发现:
1.都是这种updatetime时间列上的数据不一致。
2.updatetime列上有ON UPDATE CURRENT_TIMESTAMP 子句,也就是每次更新的时候这列的值会自动更新为当前时间。
排查:
首先我们在源库解析binlog,提取对这个表修改的sql,主要是两个sql,一个update语句和一个insert语句。
我们测试了update 语句在修改其他列或updatetime 列的情况,也测试了insert在指定updatetime值和不指定updatetime 值的情况(第一次可能测试的不是很仔细),结果都是源和目的的数据是完全一致的。
之后,根据目的数据的修改时间(2020-05-27 10:53:37),我们排查目的库的binlog,找到修改这条记录所对应的sql。
UPDATE `zhenai_crm_matchmaker`.`ArchiveMeetDetail` SET `firstMeetTime`=NULL, `updateTime`='2020-05-27 10:53:37', `miniMeetTotal`=0, `archiveTime`='2020-05-18 02:27:04', `belongDeptName`='重庆解放碑店', `workerId`=2012218, `createTime`='2020-05-19 12:20:18', `belongGroupId`=2409, `memberId`=999999999, `belongWorkerId`=2030420, `sex`=0, `belongGroupName`='红娘二部(易)', `trueName`='刘', `belongWorkerName`='罗', `meetTotal`=0, `workerName`='陈', `miniMeetThisMonth`=0, `belongDeptId`=270, `meetThisMonth`=0, `id`=2357, `archivePassTime`='2020-05-19 12:20:18' WHERE `firstMeetTime` IS NULL AND `updateTime`='2020-05-19 12:20:18' AND `miniMeetTotal`=0 AND `archiveTime`='2020-05-18 02:27:04' AND `belongDeptName`='重庆解放碑店' AND `workerId`=2030420 AND `createTime`='2020-05-19 12:20:18' AND `belongGroupId`=2409 AND `memberId`=999999999 AND `belongWorkerId`=2030420 AND `sex`=0 AND `belongGroupName`='红娘二部(易)' AND `trueName`='刘' AND `belongWorkerName`='罗' AND `meetTotal`=0 AND `workerName`='罗' AND `miniMeetThisMonth`=0 AND `belongDeptId`=270 AND `meetThisMonth`=0 AND `id`=2357 AND `archivePassTime`='2020-05-19 12:20:18' LIMIT 1; #start 142683935 end 142684421 time 2020-05-27 10:53:37
如果目的库这个时间有修改,那么源库这个时间应该也是有同样的sql在执行,顺着这条思路,查查了源库binlog,解析出了对应的sql。
UPDATE `zhenai_crm_matchmaker`.`ArchiveMeetDetail` SET `firstMeetTime`=NULL, `updateTime`='2020-05-19 12:20:18', `miniMeetTotal`=0, `archiveTime`='2020-05-18 02:27:04', `belongDeptName`='重庆解放碑店', `workerId`=2012218, `createTime`='2020-05-19 12:20:18', `belongGroupId`=2409, `memberId`=999999999, `belongWorkerId`=2030420, `sex`=0, `belongGroupName`='红娘二部(易)', `trueName`='刘', `belongWorkerName`='罗', `meetTotal`=0, `workerName`='陈', `miniMeetThisMonth`=0, `belongDeptId`=270, `meetThisMonth`=0, `id`=2357, `archivePassTime`='2020-05-19 12:20:18' WHERE `firstMeetTime` IS NULL AND `updateTime`='2020-05-19 12:20:18' AND `miniMeetTotal`=0 AND `archiveTime`='2020-05-18 02:27:04' AND `belongDeptName`='重庆解放碑店' AND `workerId`=2030420 AND `createTime`='2020-05-19 12:20:18' AND `belongGroupId`=2409 AND `memberId`=999999999 AND `belongWorkerId`=2030420 AND `sex`=0 AND `belongGroupName`='红娘二部(易)' AND `trueName`='刘' AND `belongWorkerName`='罗' AND `meetTotal`=0 AND `workerName`='罗' AND `miniMeetThisMonth`=0 AND `belongDeptId`=270 AND `meetThisMonth`=0 AND `id`=2357 AND `archivePassTime`='2020-05-19 12:20:18' LIMIT 1; #start 126420599 end 126421098 time 2020-05-27 10:53:37
单看每一边的binlog及表的数据,都没有什么问题,binlog和数据都是对应的。但问题是同步过去为什么数据不一样,当然是因为执行的sql不一样。可是执行的sql为什么不一样呢?难道同步过去binlog不一样。但是这不可能的,主库和从库的binlog应该是一样的,
那这里目的库的updatetime值为什么跟主库不一样呢?仔细看,就可以发现两点:
1.源库在修改这条数据的时候也修改了updatetime 的值,但不是修改成最新的时间,而是跟原来一样的时间。
2.而目的库,updatetime则是较新的一个值,很可能是当时执行时的时间。
基于以上两点我们又做了测试,终于发现了问题。
验证:
|
测试SQL |
结果 |
1 |
update set updatetime = '2020-05-19 12:20:18' , workerName = 'test' ... |
两边数据不一致 |
2 |
update set updatetime = '2020-05-28 10:00:00' , workerName = 'test' ... |
两边数据一致 |
我们发现出现不一致要有两个条件,
第一,updatetime的值要修改成跟原来一样,也就是updatetime的值要保持不变,
第二,除了updatetime列外,还需要变更其他列的数据。
对于源库来说,它在执行update的时候把updatetime列的值修改成跟原来一样的值(如果不这么做,那么updatetime的值就会是执行时的时间)。而binlog传到目的端,在解析binog的时候,(由于一些参数)otter会根据列的变更来同步数据,而updatetime这列修改前的时间和修改后的时间是一样的,otter就没有同步这列,比如第一个sql,otter解析执行的sql可能是这个样子的:update set workerName = 'test' ... 。由于updatetime这列是自动更新的,所以实际是这样的:update set updatetime = '#current_time#' ,workerName = 'test' ... 。这就导致了目的时间是当前时间,和源库的时间不一样。
解决:
查询资料及官方文档,我们找到了otter中的一个可能造成这种结果的参数--channel 中的同步模式,有两种同步模式:
行模式 ,兼容otter3的处理方案,改变记录中的任何一个字段,触发整行记录的数据同步,在目标库执行merge sql。
列模式 ,基于log中的具体变更字段,按需同步。
原先我们设置的是列模式,对于一般情况,同步不会有问题,但无法处理上面这种特殊情况下的同步。改为行模式后,经测试,两边的数据一致。