1.背景
公司有个模块最近有新需求,因此有些地方需要改动,而测试同学在测的过程中发现有个地方报了错让我看看,于是我看了一下报错日志,是因为数据库有重复数据,初步推断应该是在写接口导致的数据写入多条。
2.排查过程
我先是把重复数据手动删除,然后尝试复现同事的操作。发现在编辑的时候,接口返回时长特别慢,导致了报错。然后数据库多了一条重复数据,最开始的想法是,是不是在界面的操作中出现了重复点击这个问题,于是我本地连接测试数据库进行调试。
我在controller层和service层都打上了断点,然后重新调用了几次,发现有几次是产生了重复数据的,但是有几次又没有。同事之前写这段代码的时候,不是利用@Transactional
注解而是利用的手动在代码中处理事务(代码如下),我在提交事务和回滚事务的地方都打上了断点,发现有时候tx.commit(status)
会被断点拦截两次,而有时候tx.commit(status);
和tx.rollback(status);
各会被断点拦截一次,但是controller
层的断点却又只被拦截一次,于是我判断这不是来自重复点击的问题,因为如果重复点击,controller
层也会被调用两次才对。
那会是什么问题呢?我看了一下controller
层报错,报错信息如下,这段信息表名,controller
层在利用dubbo
调用service
时,出现了超时的情况,我们超时时长设置的是60s,并且设置了reties=1
,即超时重试机制,于是我明白了,应该是controller
在调用service
出现超时后,重试机制再调用了service
一次,导致了service
被执行了两次,导致事务重复提交,所以数据库出现了重复数据。
可是为什么有时候不会出现重复数据呢?我继续打断点寻找,终于,在一次没有出现重复数据时发现了以下报错。就是说在dubbo重试机制执行时,重新开启事务,但是因为前面的事务没有结束,而后面的事务操作的数据跟前一个事务操作的是一样的数据,所以导致第一个事务锁住了表,第二个事务就需要等待第一个事务执行完,但是有时候第一个事务执行的时间过长,导致第二个事务锁超时。这样也就出现了有时会出现重复数据,有时不会。
3. 总结
在使用dubbo的时候,业务逻辑不要超过dubbo设置的超时时长,不然会触发dubbo的重试机制,导致service被重复调用执行