首先,问题的现象为在系统运行过程中,未对数据库表进行更新或插入时,同一个查询sql语句,多次查询返回结果与数据库不一致,并且多次查询的结果也不一样。

数据库中的数据如下图

t_user表

id        name
1张三        
2李四
3          王五

如果这个接口的查询是这么一条查询语句一条语句 select * from where id>0;

正常的输出应该是

id        name
1张三        
2李四
3          王五

但是这条语句在程序中执行的结果可能是

id        name
1张三        
2李四

或者

id        name
1张三        
2李四
3          王五
4赵六

以及其他曾经这张表的某个时段的状态(偶发现象);并且伴随着其他提交写库的sql事物跑的很慢。

        根据这个现象分析问题,我们首先会把他分为两个问题来考虑,首先多次查询查到数据不一致的问题,想到的是可能是mybatis的一级、二级缓存问题。从这个思路出发,把mybatis的二级缓存关闭,把一级缓存的粒度设置为statement级别。

        重启服务之后发现查询数据能够查询出正常的数据了;但是在系统运行一段时间后,这个问题又出现了。

        之后把mybatis的打印sql日志的配置打开

mybatis-plus.configuration.log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

在系统运行时查询日志发现一些没有数据库事物的接口在执行时,这个sql会加入到某个已存在的事物中。

        根据这个现象作为突破口,定位这个事物id创建时,是哪条sql定位到该代码段,对代码段进行分析。最后发现该代码段是一个定时任务,并且存在有手动编程式(即注入DefaultTransactionDefinition)然后手动起一个事物,但是在try--catch中有个分支没有对该事物进行提交或者回滚,导致了这个事物一直挂起在那里,其他线程进来时就有可能加入到了这个事物中,因为数据库的事物隔离级别可重复读,所以单个事物内读取都是快照读,所以就会造成某些加入了这个事物的线程读出来的结果是之前数据库某个时刻的数据状态。

        如果某个写库的操作也加入了这个事物,这个事物在没提交,也不回滚的状态下导致业务卡死,就是出现了锁表的现象,其实是因为事物没有提交。

解释:一个线程没有正常提交事务,那么事务就会被挂起,当线程销毁后,数据库连接归还给连接池,对应的数据库连接的状态就还存在事务未提交。
其他的线程进来复用链接资源的时候,就会不知不觉被加入到已存在的事务中,甚至没有事务的查询都会加入到已存在的事务中,因为mybatisSqlSession他会先看看当前链接有没有事务,有就fetch旧的出来,没有就生成一个新的session。所以有概率出现,查询时会拿到未完成事务提交的session。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐