【全网最全】mybatis一定要禁用一级二级缓存

讲在前面

当有人把mybatis的一二级缓存问题拿出来讲,很多杠精就会说“都N年前的老技术框架知识了,怎么还拿出来讲?多次一举。”,我只想说“这篇文章想帮你分析mybatis缓存被淘汰的原因,以及帮助你在源码设计上得到更好的理解。”

先摆结论

MyBatis的一级缓存和二级缓存都需要被禁用,有别于市面上人云亦云的只禁用二级缓存,原因且听我下面分析。

实际操作:

理论懂了,实际应该怎么操作?下面的两行配置就解决:

<settings>
  <!-- ... -->
  <setting name="cacheEnabled" value="false"/>
  <!-- cacheEnabled是二是级缓存的总开关,置为false代表关闭二级缓存 -->
  <setting name="localCacheScope" value="STATEMENT"/>
  <!-- localCacheScope是本地缓存(一级缓存)的作用域,只有两种取值:SESSION和STATEMENT,取STATEMENT意味着关闭一级缓存-->
  <!-- ... -->
</settings>

单独一级缓存的工作原理和弊端

工作原理

如下1图,SqlSessionFactory成功创建之后假设与应用程序生命周期一致,那么由此Factory可创建多个SqlSession 1…n对象,每个SqlSession对象都包含一个一级缓存,每次查询数据库操作,都先查一级缓存,命中则取出,不命中则查数据库并放入一级缓存。每次更新数据库操作都清空一级缓存。后续操作都按照上述步骤进行。
在这里插入图片描述

弊端

由于一级只在一个SqlSession中共享,对于使用一个SqlSession的单线程来说是不存在脏读数据的,但是如果由多个线程同时使用多个不同的SqlSession操作数据库,由于单个SqlSession的更新操作只会清空自己的一级缓存,对于其他的SqlSession还是会继续读取其自身的缓存数据,这样就会造成脏读数据。如果你继续说多线程都只公用一个共享的SqlSession数据这样就没有你说的情况了,不妨假设一下,如果是分布式应用程序系统呢?各个分布式的系统独立运行在自己的JVM之上,很难再共享同一个SqlSession了吧。所以就算禁用二级缓存开启一级缓存也是没有办法避免脏读数据的。

二级缓存的工作原理和弊端

原理

二级缓存的粒度换份呢是按照一个Mapper接口(也就是命名空间)对应一个二级缓存,二级缓存维护再SqlSessionFactory的Configuration对象中,所以二级缓存的生命周期和SqlSessionFactory是等效的,也就是和应用程序是等效的。所以每一个生成的SqlSession都可通过内含的Configuration对象访问到二级缓存。

工作流程如下图所示:每个SqlSession在操作数据库时会先分析这个操作属于哪个Mapper接口(也就是命名空间),会去对应的二级缓存中查找匹配项,找到则返回,没有找到则继续查找自己对应的单独一级缓存,同理继续查找。这样就可以在多个SqlSession之间共享数据,从而避免一级缓存对于同时使用多个SqlSession造成的脏读现象。

在这里插入图片描述

弊端

但是,这样的二级缓存+一级缓存设计有一个致命的缺陷,过程有点复杂,我就用手绘的示意图代替:
在这里插入图片描述

① 同一个SqlSessionFactory两个不同的SqlSession A和B共享同一个namespace下的二级缓存,有各自的一级缓存。如果其中SqlSession B做更新操作会清空共享的二级缓存,那么SqlSession A再去查询的时候,不会在二级缓存中找到数据,转而在一级缓存中找到数据,这样就造成了脏读数据。

由于我在各大论坛博客都没有搜索到这个问题,所以还去各个技术交流群询问了一下。

在这里插入图片描述
在这里插入图片描述

② 同时给二级缓存还会存在其他的问题,比如:不同命名空间的修改可能会导致其他命名空间数据脏读,如果能用cache-ref和按照良好的规范分表来妥善处理还好说,如果没处理好可能都不知道什么地方出了错。再比如,由于二级缓存只在同一个SqlSessionFactory中共享,所以和一级缓存一样,分布式的场景没有办法彻底解决脏读。

综上所述

建议禁用一级与二级缓存,分布式应用程序应该是必须要禁用的,单体应用如果没有禁用一级缓存,则要建议多线程共享同一个SqlSession。

最后

对于禁用一级缓存的配置很多答者都说不可以禁用,其实不是的,观看源码可以发现,每次执行数据库查询操作时都会判断Configuration的localCacheScope字段的枚举类型,如果是SESSION ,则代表开启一级缓存,如果是STATEMENT则代表关闭一级缓存,每次查询完之后都会清除掉一级缓存的内容,从而达到禁用一级缓存的效果。下图是BaseExecutor的query()方法源码,是真正开始执行数据库操作的部分。
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/liangcheng0523/article/details/106870415