表重复更新引发的问题

Posted by Night Field's Blog on November 27, 2019

问题描述

我们的工程部署在两个DC上,双活。两边的服务器以及逻辑都是一样的,也分别都有自己的业务。数据库用的Oracle,之间有同步,用的是Quest的DB同步产品shareplex。shareplex的原理是,read进程读取分析数据库的redo log,把需要更新的数据放到queue里面,export进程读取queue的数据,发送到对端的queue里面,由对端的import进程写入对端数据库。

虽然两个DC只接收存储单边的数据,但是两边数据库都是有完整的数据的。

数据复制

然而最近发现奇怪的问题,两边的数据一直对不上,导致很多依赖数据完整性的功能全都错乱了。

问题定位

既然是数据不一致,那我们就从数据库同步入手。我们联系了DBA,同时检查项目里面有没有大量的增删改操作。对并发量大的几张表有修改的地方我们都过了一遍,发现并没有什么可疑的地方。

等DBA回消息,果然,他们观察到shareplex的复制队列有大量的backlog。然而令我们没想到的是,堵在队列里的竟然是对一张数据量并不大的表A的操作。A是一张maintain表,记录着哪些component正处在维护状态。每条记录都必须关联一个ticket,ticket的状态有New,InProgress,Done,Completed,Cancelled等等。一般来说这张表的数据只有几万条,怎么会有那么多增删改操作在这上面呢?!

追溯到问题刚开始出现的那个时间节点,我们是上了一个新功能。简单描述就是,因为我们需要保证maintain表A里的ticket状态信息是最新的,我们新加了一个Task,定时从源头同步ticket状态。于是我们仔细看了这块逻辑,看出了端倪。Code的逻辑是:

  1. 从数据库取出所有状态不是Complete的数据
  2. foreach处理,从源头拿到当前数据对应的ticket的状态信息
  3. 执行更新操作,伪代码如下:update maintain set status = #{status} where ticket = #{ticket}

乍一看,好像是没什么问题,但是一细想,我们的maintain数据跟ticket的关系是多对一的,也就是说,一个ticket可以跟好多的maintain数据关联!于是乎,在这种情况下,每次的update操作都会更新多条数据,而且会更新多次!如果有100条一样的ticket,那就会执行100*100也就是一万次操作。如果是一万条一样的ticket呢,那就是一亿次更新操作!而且还仅仅是单次的量,考虑到task是定时跑的,量级只能更多。shareplex是撑不住这么大的量的。

问题解决

找到了问题,对症下药:

  1. 只有在ticket状态有变化的时候才去更新数据库
  2. 当更新完一个ticket的时候,把ticket放到Set里面,后续操作如果发现相同ticket已经被更新过了,就直接跳过

总结

  1. 碰到问题时,要特别留意新增的功能。因为对于比较稳定的项目来说,新增的功能出问题的概率要远大于老功能。
  2. 当遇到在循环里面更新数据库的情况,要特别留意是不是会导致数据重复更新。这不仅无端增加了数据库的压力,而且可能给数据库之间的复制带来灾难。