Bootstrap

mybatis一次排查更新操作后,查询却是缓存的问题

mybatis一次排查更新操作后,查询却是缓存的问题

事件起因

我在一个分布式的环境下,一个服务调用另一个服务的更新操作,然后接着去查询这个记录,结果发现,这个服务的查询结果还是原来的。

猜测

查询到还是原来的,无非就两种可能,一个就是更新没更新成功,这个是没有可能的,因为我查询数据库,发现记录是改变了的。那就是说更新操作是成功了的,那么就只有另一种可能了,哪个地方有缓存。

先开启下这个服务的日志,打印请求和mybatis执行的sql的日志。结果确实更新执行了语句,而get操作没执行,走的缓存。

猜测1.fegin有缓存?

fegin说到底还是一个httpclient,我就理解为一个连接发送一个请求,那么就应该是没有缓存的,除非自己实现了缓存,我这个调用服务是没缓存fegin的结果的。

猜测2.mabtis的缓存?

那么我就只能怀疑到mybatis的自带的一级缓存和开启的二级缓存。

mybatis的一级缓存是mapper级别的,一级缓存是SqlSession级别的缓存。在操作数据库时需要构造sqlSession对象,在对象中有一个数据结构用于存储缓存数据。不同的sqlSession之间的缓存数据区域是互相不影响的。也就是他只能作用在同一个sqlSession中,不同的sqlSession中的缓存是互相不能读取的。

还有一个原因,实际开发中,MyBatis通常和Spring进行整合开发。Spring将事务放到Service中管理,对于每一个service中的sqlsession是不同的,这是通过mybatis-spring中的org.mybatis.spring.mapper.MapperScannerConfigurer创建sqlsession自动注入到service中的。 每次查询之后都要进行关闭sqlSession,关闭之后数据被清空。所以spring整合之后,如果没有事务,一级缓存是没有意义的。

简单的来说,spring开启事务后,一次数据库连接里,同条sql会走的缓存,但是即增删改操作时会清空缓存。

明显我调用的时候,我是两个请求,而且我是改操作了,所以不会是mybatis的一级缓存。

那是二级缓存?

二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。
UserMapper有一个二级缓存区域(按namespace分),其它mapper也有自己的二级缓存区域(按namespace分)。每一个namespace的mapper都有一个二级缓存区域,两个mapper的namespace如果相同,这两个mapper执行sql查询到数据将存在相同的二级缓存区域中。
开启二级缓存:
1,打开总开关
在MyBatis的配置文件中加入:
<settings>    
   <!--开启二级缓存-->    
    <setting name="cacheEnabled" value="true"/>    
</settings> 
2,在需要开启二级缓存的mapper.xml中加入caceh标签
<cache/>
3,让使用二级缓存的POJO类实现Serializable接口
public class User implements Serializable 


对于查询多commit少且用户对查询结果实时性要求不高,此时采用mybatis二级缓存技术降低数据库访问量,提高访问速度。
但不能滥用二级缓存,二级缓存也有很多弊端,从MyBatis默认二级缓存是关闭的就可以看出来。
二级缓存是建立在同一个namespace下的,如果对表的操作查询可能有多个namespace,那么得到的数据就是错误的。
举个简单的例子:
订单和订单详情,orderMapper、orderDetailMapper。在查询订单详情时我们需要把订单信息也查询出来,那么这个订单详情的信息被二级缓存在orderDetailMapper的namespace中,这个时候有人要修改订单的基本信息,那就是在orderMapper的namespace下修改,他是不会影响到orderDetailMapper的缓存的,那么你再次查找订单详情时,拿到的是缓存的数据,这个数据其实已经是过时的。
 
根据以上,想要使用二级缓存时需要想好两个问题:
1)对该表的操作与查询都在同一个namespace下,其他的namespace如果有操作,就会发生数据的脏读。
2)对关联表的查询,关联的所有表的操作都必须在同一个namespace。

可以看出,二级缓存是可能在不同的sqlsession里缓存数据,导致我调用获取的还是原来的数据。、

但是我的namespace没加cache标签

于是我关闭了在MyBatis的配置文件的

<setting name="cacheEnabled" value="false"/>

重试了下,还是走的缓存。

然后我又去看了下mybatis的配置

<!-- 设置本地缓存范围 session:就会有数据的共享  statement:语句范围 (这样就不会有数据的共享 ) defalut:session -->
     <setting name="localCacheScope" value="SESSION"/>

我发现,这个设置的是session。我这里理解出错了,我以为是request的session,然后我想了下,fegin的连接是长连接还是短连接,百度了下,fegin的连接,

https://www.cnblogs.com/crazymakercircle/p/11832534.html

我突然想到了,我今天调用fegin,发现post请求失败,结果是我没替换fegin的底层实现,所以我替换fegin的底层实现为httpclient,然后我百度了下发现httpclient是长连接,确实我现在的fegin是长连接,那应该是同一个session。

然后我就改了配置

<!-- 设置本地缓存范围 session:就会有数据的共享  statement:语句范围 (这样就不会有数据的共享 ) defalut:session -->
     <setting name="localCacheScope" value="STATEMENT"/>

重试了下,发现还是走缓存。

这里出错是我理解错了session。MyBatis 利用本地缓存机制(Local Cache)防止循环引用(circular references)和加速联复嵌套査询。
默认值为 SESSION,这种情况下会缓存一个会话中执行的所有查询。若设置值为 STATEMENT,本地会话仅用在语句执行上,对相同 SqlScssion 的不同调用将不会共享数据 。

这个session应该是mybatis的sqlsession。一次事务中的数据缓存。

猜测3.语句中用到了第三方缓存

然后我去看了下语句,发现,emmm。用到了redis的缓存。。。。。

我绕这么一大圈,然后,发现是这个,我心态有点崩。

解决方案

更新操作的时候,去掉key或者更新值都可以。

http://c.biancheng.net/view/4324.html

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;