一、背景
今天遇到了一个很奇葩的bug,是这样的。我在一个service里面执行了4步操作(流程图如下):
1)给表1新增了一条数据;
2)根据表1新增的结果,给表2新增一条数据;
3)根据表2插入的结果,异步调用第三方接口。然后他们处理完了之后以MQ的形式告诉我的服务;
4)如果调用第三方接口成功,继续处理剩下的逻辑。
遇到的问题是:我收到第三方的MQ后,有时查表1中新增的数据查不到,但是数据库里面明明有的啊。另外,为啥偶尔查不到呢?难道有代码玄学在作怪么?
二、原因
究其原因,是 数据库事务在作怪,也怪我当初学习的时候只是背了概念,没有深刻的去理解它。当时代码上面有有一个注解@Transactional,看了它的源码,不声明隔离级别时,它是使用数据库默认的隔离级别。我们知道mysql默认的隔离级别时“可重复读” (读未提交——读已提交——可重复读——串行化)。所以就清楚了,当收到第三方的MQ之后,由于之前的事务没有提交,所以当然查不到数据了。
@Transactional(rollbackFor = {RuntimeException.class, OptimusExceptionBase.class})
@Override
public void xxx(){
//逻辑处理
}
/** * Use the default isolation level of the underlying datastore. * All other levels correspond to the JDBC isolation levels. * @see java.sql.Connection */ Isolation isolation() default Isolation.DEFAULT;
三、解决办法
方案一:在收到MQ的那个service里面,可以将隔离级别设置为“读未提交”;
方案二:在调用第三方的那个service里面,去掉事务,这样只要有数据库的“写操作”,则立马会得到数据库的响应,不会受后续代码写数据库的影响,完全是单独的。当然缺点是需要自己控制事务的执行与回滚;
方案三:可能情况下,调整代码流程,将调用第三方的操作放在最后,调用第三方结束则方法结束。
四、其他情况
还有一种情况也会发生“插入数据,读取不到”。当数据库配置了读写分离后,写数据库会写主库,而读数据会读从库。这样在网络延迟比较高的情况下,可能发生主从延迟——在主库新插入的数据还没有同步到从库,导致查询从库查询不到。对于这种问题,解决办法就好多了,具体用那种根据自己的业务决定:
1)降低主从延迟;
2)优化代码逻辑,为啥插入了还要读取;
3)开启半同步复制,即数据同步到了从库才算真正的插入。