问题描述
1、查询出来数据库已经删除的数据
2、增删改操作出现事务锁超时
操作过程:
第一次导入无异常数据无异常成功,
第二次导入异常数据抛出异常,此时去数据库删除数据,点击查询接口出现了第一次导入成功的数据
再次导入无异常数据出现锁超时问题
问题解决
- 首先是怀疑是不是查询用到了Mybatis的缓存,修改项目配置禁用Mybatis的一级缓存
mybatis:
configuration:
cache-enabled: false #禁用二级缓存
local-cache-scope: statement #一级缓存指定为statement级别
- 重启服务后进行测试,发现之前删除的数据不会查询出来了,但是!重现刚刚的操作,问题又出现了,排除Mybatis缓存问题。
- 根据锁超时的问题,怀疑自己写的手动事务导致的,注释代码重现操作,问题没有出现,查阅相关资料发现问题。
/**
* 事务管理器
*/
private final PlatformTransactionManager platformTransactionManager;
/**
* 事务状态
*/
private final TransactionStatus transactionStatus;
查询控制台Mybatis的日志发现,在新增异常数据的时候,新创建了一个SqlSession@43b0133f
这个操作开启了事务所有操作都是Fetched SqlSession加入到当前的Session。请求查询接口,发现自动加入了之前@43b0133f的Session查询出了当时快照的数据,所以出现了问题1
二次查询日志
4. 存在有手动编程式(即注入DefaultTransactionDefinition),但是在try–catch中有个分支没有对该事物进行提交或者回滚,导致了这个事物一直挂起在那里,其他线程进来时就有可能加入到了这个事物中,因为数据库的事物隔离级别是可重复读,所以单个事物内读取都是快照读,所以就会造成某些加入了这个事物的线程读出来的结果是之前数据库某个时刻的数据状态。
5. 如果某个写库的操作也加入了这个事物,这个事物在没提交,也不回滚的状态下导致业务卡死,就是出现了锁表的现象,其实是因为事物没有提交。
6. 所以基于4-5点,只需要在最外一层调用者try-catch中回滚事务就解决了
// 回滚事务
platformTransactionManager.rollback(transactionStatus);
总结:
一个线程没有正常提交事务,那么事务就会被挂起,当线程销毁后,数据库连接归还给连接池,对应的数据库连接的状态就还存在事务未提交。其他的线程进来复用链接资源的时候,就会不知不觉被加入到已存在的事务中,甚至没有事务的查询都会加入到已存在的事务中,因为MybatisSqlSession他会先看看当前链接有没有事务,有就fetch旧的出来,没有就生成一个新的Session。所以有概率出现,查询时会拿到未完成事务提交的Session。