由事务引起的一次问题排查

现象

今天同事让我帮忙看一个问题,在写单元测试(下文称为delTest)测试del方法。该del方法中依次做了三件事:

  1. 从数据库中删除某条数据
  2. 将该条数据的对应的缓存清空
  3. 从缓存中查该条数据,check获取到null
  4. 发送数据被数据变化的mq消息

delTest中断言:

  1. 数据库中查不到该条数据
  2. 缓存中查不到该条数据

就是这样的一条简单的CASE竟然报错,断言2不满足,即缓存中依然能访问到该条数据。

过程

看似没有问题,打个断点调试一下。思路是:

  1. 清缓存调用的前后打断点;记录调用前缓存的创建时间,观察调用后缓存是否被清除
  2. 获取delTest中查到的缓存的创建时间,看是否是同一份缓存

结论是的确生成了新的缓存,而非缓存清除失败!

那么问题的重点应该放在在清除缓存后的哪里可能会生成新的缓存?我发现发送数据变化的mq消息会被搜索团队监听,而且他们会拿消息中的id反查发生了怎样的变化,而在反查的find方法中会先查缓存,如果没有就从数据库中查数据,并把结果存进缓存。这里就奇怪了,数据都已经确认删除了(断言1已经成立),怎么还会查到数据?

清除缓存,发送消息…咦~del操作是包在一个事务中,会不会是事务引起的?我猜想,在发送消息的时候,搜索听到消息来查,此时事务并没有提交,find操作仍然能从数据库中查到该条数据,并加入到缓存。看起来像是事务的隔离性引发的异常。

验证

暂时去掉事务注解,发现delTest这条case通过了。但是简单去掉事务注解是不行的,有一些批量数据库操作是需要保证事务性。因此将删除数据库的操作放进了事务里面,提交后在清除缓存,发送mq消息。

总结

我们总说事务的四大特性:原子性,一致性,持久性,隔离性。如果只是背下来而不能理解之,那么只知道这几个术语又有什么用呢?

作者: wuzhaoyang(John)
出处: http://wuzhaoyang.me/
因为作者水平有限,无法保证每句话都是对的,但能保证不复制粘贴,每句话经过推敲。希望能表达自己对于技术的态度,做一名优秀的软件工程师。