0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

为什么不推荐使用MyBatis二级缓存

jf_ro2CN3Fa 来源:芋道源码 2023-07-16 15:32 次阅读


为了增加查询的性能,mybatis 提供了二级缓存架构,分为一级缓存和二级缓存。

这两级缓存最大的区别就是:一级缓存是会话级别的,只要出了这个 SqlSession,缓存就没用了。而二级缓存可以跨会话,多个会话可以使用相同的缓存!

一级缓存使用简单,默认就开启。二级缓存需要手动开启,相对复杂,而且要注意的事项也多,否则可能有隐患。

一级缓存

应用场景

订单表与会员表是存在一对多的关系,为了尽可能减少join查询,进行了分阶段查询。即先查询出订单表,再根据member_id字段查询出会员表,最后进行数据整合。而如果订单表中存在重复的member_id,就会出现很多重复查询。

针对这种情况,mybatis通过一级缓存来解决:在同一次查询会话(SqlSession)中如果出现相同的语句及参数,就会从缓存中取出,不再走数据库查询。

一级缓存只能作用于查询会话中,所以也叫做会话缓存。

生效的条件

一级缓存要生效,必须满足以下条件条件:

  • 必须是相同的会话
  • 必须是同一个 mapper,即同一个 namespace
  • 必须是相同的 statement,即同一个 mapper 中的同一个方法
  • 必须是相同的 sql 和参数
  • 查询语句中间没有执行 session.clearCache() 方法
  • 查询语句中间没有执行 insert/update/delete 方法(无论变动记录是否与缓存数据有无关系)

与springboot集成时一级缓存不生效原因

e2e30ca0-2388-11ee-962d-dac502259ad0.png

因为一级缓存是会话级别的,要生效的话,必须要在同一个 SqlSession 中。但是与 springboot 集成的 mybatis,默认每次执行sql语句时,都会创建一个新的 SqlSession!所以一级缓存才没有生效。

当调用 mapper 的方法时,最终会执行到 SqlSessionUtilsgetSqlSession 方法,在这个方法中会尝试在事务管理器中获取 SqlSession,如果没有开启事务,那么就会 new 一个 DefaultSqlSession

e3048fc4-2388-11ee-962d-dac502259ad0.png

所以说,即便在同一个方法中,通过同一个 mapper 连续调用两次相同的查询方法,也不会触发一级缓存。

解决与springboot集成时一级缓存不生效问题

在上面的代码中也看到了,mybatis 在查询时,会先从事务管理器中尝试获取 SqlSession,取不到才会去创建新的 SqlSession。所以可以猜测只要将方法开启事务,那么一级缓存就会生效。

加上 @Transactional 注解,看下效果:

e3163832-2388-11ee-962d-dac502259ad0.png

没错,的确生效了。在代码中可以看到,从事务管理器中,获取到了 SqlSession:

e33ab5b8-2388-11ee-962d-dac502259ad0.png

再看看源码中是什么时候将 SqlSession 设置到事务管理器中的。

SqlSessionUtils 中,在获取到 SqlSession 后,会调用 registerSessionHolder 方法注册 SessionHolder 到事务管理器:

e35705b0-2388-11ee-962d-dac502259ad0.png

具体是在 TransactionSynchronizationManagerbindResource 方法中操作的,将 SessionHolder 保存到线程本地变量(ThreadLocal) resources 中,这是每个线程独享的。

e376d0a2-2388-11ee-962d-dac502259ad0.png

然后在下次查询时,就可以从这里取出此 SqlSession,使用同一个 SqlSession 查询,一级缓存就生效了。

所以基本原理就是:如果当前线程存在事物,并且存在相关会话,就从 ThreadLocal 中取出。如果没有事务,就重新创建一个 SqlSession 并存储到 ThreadLocal 当中,共下次查询使用。

至于缓存查询数据的地方,是在 BaseExecutor 中的 queryFromDatabase 方法中。执行 doQuery 从数据库中查询数据后,会立马缓存到 localCache(PerpetualCache类型) 中:

e393ccd4-2388-11ee-962d-dac502259ad0.png

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/ruoyi-vue-pro
  • 视频教程:https://doc.iocoder.cn/video/

二级缓存

应用场景

业务系统中存在很多的静态数据如,字典表、菜单表、权限表等,这些数据的特性是不会轻易修改但又是查询的热点数据。

一级缓存针对的是同一个会话当中相同SQL,并不适合这情热点数据的缓存场景。

为了解决这个问题引入了二级缓存,它脱离于会话之外,多个会话可以使用相同的缓存。

看一个例子:

@RestController
@RequestMapping("item")
publicclassItemController{

@Autowired
privateItemMapperitemMapper;

@GetMapping("/{id}")
publicvoidgetById(@PathVariable("id")Longid){
System.out.println("====================begin====================");
Itemitem=itemMapper.selectById(id);
System.out.println(JSON.toJSONString(item));
}

}

当发送两次 get 请求时(两个不同的会话),通过日志可以发现第二次查询使用的是缓存

e3ad30d4-2388-11ee-962d-dac502259ad0.png

开启的方法

二级缓存需要手动来开启,mybatis 默认没有开启二级缓存。

1)在 yaml 中配置 cache-enabled 为 true

mybatis-plus:
configuration:
cache-enabled:true

2)Mapper 接口上添加 @CacheNamespace 注解

e3bec9c0-2388-11ee-962d-dac502259ad0.png

3)实体类实现 Serializable 接口

e3d060f4-2388-11ee-962d-dac502259ad0.png

生效的条件

  • 当会话提交或关闭之后才会填充二级缓存
  • 必须是同一个 mapper,即同一个命名空间
  • 必须是相同的 statement,即同一个 mapper 中的同一个方法
  • 必须是相同的 SQL 语句和参数
  • 如果 readWrite=true(默认就是true),实体对像必须实现 Serializable 接口

缓存清除条件

  • 只有修改会话提交之后,才会执行清空操作
  • xml 中配置的 update 不能清空 @CacheNamespace 中的缓存数据
  • 任何一种增删改操作都会清空整个 namespace 中的缓存

源码中是如何填充二级缓存的?

在生效条件中提到了,二级缓存必须要在会话提交或关闭之后,才能生效!

在查询到结果后,会调用 SqlSession 的 commit 方法进行提交(如果开启事务的话,提交 SqlSession 走的不是这里了,但最终填充二级缓存的地方是一样的。):

e3e06576-2388-11ee-962d-dac502259ad0.png

在此方法中,最终会调用到 TransactionalCacheflushPendingEntries 方法中填充二级缓存:

e4081436-2388-11ee-962d-dac502259ad0.png

springboot 集成 mybatis 的话,如果没有开启事务,每次执行查询,都会创建新的 SqlSession,所以即使是在同一个方法中进行查询操作,那也是跨会话的。

查询时如何使用二级缓存?

在查询的时候,最终会调用 MybatisCachingExecutor 的 query 方法,里面会从 TransactionalCacheManager 中尝试根据 key 获取二级缓存的内容。

可以看到,这个 key 很长,由 mapper、调用的查询方法、SQL 等信息拼接而成,这也是为什么想要二级缓存生效,必须满足前面所说的条件。

如果能在二级缓存中查询到,就直接返回了,不需要访问数据库。

e4219e74-2388-11ee-962d-dac502259ad0.png

具体的调用层数实在太多,用到了装饰者模式,最终是在 PerpetualCache 中获取缓存的:

e43846f6-2388-11ee-962d-dac502259ad0.png

打印日志是在 LoggingCache 中:

e44f3a64-2388-11ee-962d-dac502259ad0.png

为什么mybatis默认不开启二级缓存?

答案就是,不推荐使用二级缓存!

二级缓存虽然能带来一定的好处,但是有很大的隐藏危害!

它的缓存是以 namespace(mapper) 为单位的,不同 namespace 下的操作互不影响。且 insert/update/delete 操作会清空所在 namespace 下的全部缓存。

那么问题就出来了,假设现在有 ItemMapper 以及 XxxMapper,在 XxxMapper 中做了表关联查询,且做了二级缓存。此时在 ItemMapper 中将 item 信息给删了,由于不同 namespace 下的操作互不影响,XxxMapper 的二级缓存不会变,那之后再次通过 XxxMapper 查询的数据就不对了,非常危险。

来看一个例子:

@Mapper
@Repository
@CacheNamespace
publicinterfaceXxxMapper{

@Select("selecti.iditemId,i.nameitemName,p.amount,p.unit_priceunitPrice"+
"fromitemiJOINpaymentponi.id=p.item_idwherei.id=#{id}")
ListgetPaymentVO(Longid);

}


@Autowired
privateXxxMapperxxxMapper;

@Test
voidtest(){
System.out.println("====================查询PaymentVO====================");
ListvoList=xxxMapper.getPaymentVO(1L);
System.out.println(JSON.toJSONString(voList.get(0)));
System.out.println("====================更新item表的name====================");
Itemitem=itemMapper.selectById(1);
item.setName("java并发编程");
itemMapper.updateById(item);
System.out.println("====================重新查询PaymentVO====================");
ListvoList2=xxxMapper.getPaymentVO(1L);
System.out.println(JSON.toJSONString(voList2.get(0)));
}

上面的代码,test()方法中前后两次调用了 xxxMapper.getPaymentVO 方法,因为没有加 @Transactional 注解,所以前后两次查询,是两个不同的会话,第一次查询完后,SqlSession 会自动 commit,所以二级缓存能够生效;

然后在中间进行了 Item 表的更新操作,修改了下名称;

由于 itemMapperxxxMapper 不是同一个命名空间,所以 itemMapper 执行的更新操作不会影响到 xxxMapper 的二级缓存;

再次调用 xxxMapper.getPaymentVO,发现取出的值是走缓存的,itemName 还是老的。但实际上 itemName 在上面已经被改了!

执行日志如下:

e46a30f8-2388-11ee-962d-dac502259ad0.png

所以说,二级缓存的隐藏危害是比较大的,当有表关联时,一个不注意就会出问题,不建议使用。


声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 缓存
    +关注

    关注

    1

    文章

    220

    浏览量

    26443
  • 管理器
    +关注

    关注

    0

    文章

    233

    浏览量

    18319
  • mybatis
    +关注

    关注

    0

    文章

    57

    浏览量

    6646

原文标题:为什么不推荐使用 MyBatis 二级缓存

文章出处:【微信号:芋道源码,微信公众号:芋道源码】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    #硬声创作季 SSM整合教程:54-MyBatis二级缓存

    缓存spring系统架构
    Mr_haohao
    发布于 :2022年09月19日 08:51:18

    CPU二级缓存打开器-加速

    不会给你最大的频率的,所以可以超频的,而这个软件就是在超频的情况下,打开CPU的二级缓存,充分发挥 CPU的工效!一定要搞清楚你的CPU二级缓存大小啊!一定!!!!  问问别人或者直接看一下你的CPU说明书
    发表于 03-02 17:04

    求大神指点 关于fpga实现数据的二级缓存

    如论文所示,应该如何实现二级缓存处理呢?四路信号是有pi/4相位差的方波信号
    发表于 06-21 18:29

    Java程序员笔记之mybatis结合redis实战二级缓存

    Java程序员笔记——mybatis结合redis实战二级缓存
    发表于 06-10 09:15

    二级缓存一致性

    裸机下运行多核程序,开128KB的二级缓存时,结果没问题,但是开了256KB的二级缓存时,结果出现偏差,请问这种问题该怎么排查解决
    发表于 08-24 21:40

    L220高速缓存控制器技术参考手册

    当处理器产生大量内存流量时,添加片上二级缓存(也称为二级二级缓存)是提高基于ARM的系统性能的公认方法。根据定义,二级缓存假定存在与处理器紧密耦合或内部的一
    发表于 08-02 15:09

    L210高速缓存控制器技术参考手册

    当中央处理器(CPU)产生大量内存流量时,添加片上二级缓存(也称为二级缓存,L2CC)是提高计算机系统性能的公认方法。根据定义,二级缓存假定存在与CPU紧密耦合或内部的一
    发表于 08-02 13:11

    CPU二级缓存容量

    CPU二级缓存容量            CPU缓存(Cache Memory)是位
    发表于 12-24 10:22 485次阅读

    什么是联合并行处理二级缓存

    什么是联合并行处理二级缓存? 联合并行处理二级缓存是(set-associative)将二级缓存划分不同的片段,在每一片段中包含许多缓存线
    发表于 02-04 10:34 299次阅读

    什么是CPU一级缓存/二级缓存

    什么是CPU一级缓存/二级缓存?  即L1 Cache。集成在CPU内部中,用于CPU在处理数据过程中数据的暂时保存。由于缓存指令和数据与CPU
    发表于 02-04 10:43 1025次阅读

    Mybatis缓存之一级缓存

    本文主要讲mybatis的一级缓存,一级缓存是SqlSession级别的缓存mybatis提供查询缓存
    发表于 11-27 20:44 1082次阅读
    <b class='flag-5'>Mybatis</b><b class='flag-5'>缓存</b>之一级<b class='flag-5'>缓存</b>

    怎样启用CPU的二级缓存 如何查看二级缓存的参数

    提到二级缓存容量的差距,还得从两大CPU巨头对一级缓存的理解说起。对,没看错,就是平常曝光率远逊于二级缓存的“一级缓存”!它才是造成上面提到巨大差异的“罪魁祸首”。
    的头像 发表于 08-14 10:20 1w次阅读

    如何检测cpu二级缓存是否损坏 详解二级缓存对CPU性能影响

    综上所述,在CPU性能方面,并非只从二级缓存容量上作对比就可以得到准确的答案,实际上还要考虑到缓存的总体设计结构、一级数 据缓存容量等因素。
    发表于 08-14 10:39 9390次阅读
    如何检测cpu<b class='flag-5'>二级缓存</b>是否损坏 详解<b class='flag-5'>二级缓存</b>对CPU性能影响

    二级缓存速度如何 二级缓存最大多少

    缓存大小也是CPU的重要指标之一,且缓存的结构和大小对CPU速度的影响大,CPU内缓存的运行频率极高,一般是和处理器同频运作,工作效率大于系统内存和硬盘。
    发表于 08-14 15:01 9443次阅读

    mybatis一级缓存二级缓存的原理

    MyBatis是一种轻量级的持久化框架,它提供了一级缓存二级缓存的机制来优化数据库操作性能。一级缓存是默认开启的,而二级缓存需要手动配置启
    的头像 发表于 12-03 11:55 518次阅读