修改查询结果导致脏读问题的保护性拷贝方案 和关闭Mybatis缓存方案对比
发布日期:2021-07-30 03:26:42 浏览次数:2 分类:技术文章

本文共 1897 字,大约阅读时间需要 6 分钟。

由于业务需求,需开发一个复杂查询页面。

 

最外层是车主数据,每个车主可展开列表查看车主名下的车辆信息,点击更多可查看车辆的多段包期。

实现过程为,先通过筛选项停车库、车主姓名、车牌号码、所属组织、在场状态、占位状态关联表查询出车主数据Result1,所用sql为SQL1;再通过停车库、SQL1查出的车主、车牌号码、在场状态、占位状态关联表查询出所有车辆信息,所用sql为SQL2,填充到Result1相应字段中(此过程称为PROCESS1),变成Result2。如下图:

 

但由于车位数、剩余车位数是属于车主的数据,应与筛选项车牌号码、在场状态、占位状态无关,不应随着车辆查询结果变动。所以应再需忽略车牌号码、在场状态、占位状态筛选项,对车辆信息再一次进行查询,得到的结果用来计算车位数和剩余车位数,填充到结果中。过程为再次调用SQL1获取车主数据Result3,再根据停车库、SQL1查出的车主关联表查询出所有车辆信息,所用sql为SQL3,填充到Result3相应字段中(此过程称为PROCESS2),变成Result4。最后在Result2合并封装转化为最终返回结果过程中,将Result4计算出的车位数、剩余车位数填充到结果中。如下图:

 

然而遇到一个问题,最终得到的结果中,有效期数据均多了重复的一份。

 

在原本的想象中,Result3的数据应与Result1相同,Result2的数据应该不受后续查询结果的影响。然而Result2的数据被后续操作修改了,有效期数据多了重复的一份,而经过调试发现,Result3的数据也与Result1不同。

 

调试得知是再次调用SQL1获取车主数据Result3时出了问题,得到的其实是Result2对象。因为Result2对象是直接修改Result1对象得到的,而Mybatis默认开启了一级缓存,当在同一个SQLSession中,查询语句和参数均相同时,会使用缓存,不再次进行SQL查询,直接使用上次查询结果,而上次查询结果被修改为了Result2。在Result2上进行操作,使得PROCESS1和PROCESS2作用在了一个对象上,造成有效期数据多了一份。

其实这就是数据库脏读,读取了未提交的被修改的数据,那为什么事务没有起作用呢?因为我们的事务配置在action层,而此查询业务是在同一个请求里面。

 

查看日志,确实SQL1只执行了一遍:

 

另外,由于修改查询结果再更新的操作普遍应用,无法确定为只是修改查询结果还是修改查询结果之后再进行更新操作,无法进行限制。

 

知道原因后,这里有两种方案可解决这个问题。

方案一为关闭SQL1的Mybatis一级缓存(本项目只使用了一级缓存),每次查询时清空本地缓存,使得Result3的数据是新一次查询得出的原始数据,与Result1数据一样,未进行PROCESS1。且再进行的PROCESS2也与之前的查询结果Result2对象无关了。

做法为在Mapper文件中,对应的SQL语句中,加上useCache="false" flushCache="true"。

 

tips:

select语句中,flushCache默认为false,表示任何时候语句被调用,都不会去清空本地缓存和二级缓存。

useCache默认为true,表示会将本条语句的结果进行二级缓存。

 

查看日志,SQL1执行了两遍:

 

方案二为,再次调用SQL1获取车主数据获取缓存数据Result2后,进行保护性拷贝,新建对象,将原对象属性复制到新对象,且将PROCESS1填充的数据清除,即得到与Result1一样的数据Result3,且对Result3的操作不影响Result2。如下图:

 

 

两种方案都能得到正确的结果。

 

接下来对两种方案的性能进行简单测试,查询10次的数据如下:

两万个车主两万辆车,每页20条数据。

方案一:

方案二:

 

每页100条数据,

方案一:

方案二:

 

不同方案性能对比

2万车主2万车辆

请求相应时间(单位ms)

平均值

每页20条

方案一

786

700

672

721

694

706

715

723

718

699

713.4

方案二

372

270

381

396

424

429

410

403

399

402

388.6

每页100条

方案一

843

841

779

849

976

919

911

977

926

926

894.7

方案二

564

523

647

542

467

458

548

622

649

629

564.9

 

粗略测试得出方案二性能相对更好,因为少了一次数据库查询,最终选择方案二。

转载地址:https://blog.csdn.net/u010898743/article/details/100067890 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:《重构-改善既有代码的设计》总结
下一篇:spring配置错误重复扫描导致事务失效的问题解决

发表评论

最新留言

做的很好,不错不错
[***.243.131.199]2024年04月10日 10时38分11秒