Mybatis的缓存机制引发的问题
Mybatis的缓存机制引发的问题解析
最近我参与了一个图像识别的项目,并且遇到了一个奇怪的Bug。通过反复的调试,我才发现问题的根源在于Mybatis的缓存机制。在此文章中,我将向大家分享这段代码以及我的处理方法。
代码说明及问题分析
下面的代码是核心逻辑,使用aiQueueService这个类来查询数据表中是否还有未识别的数据(status=0)。如果没有,就结束while循环;如果有,就把数据更新为已识别(status=1)。
// 代码块1
while (flag) {
log.info("循环获取queue数据...");
queue = aiQueueService.getOne(new QueryWrapper<AiQueue>()
.eq("status",0)
.last("limit 1"));
if (queue == null) {
queueFlag = false;
DATASTATUS = false;
log.info("系统无可识别数据");
break;
}
queueFlag = aiQueueService.update(new UpdateWrapper<AiQueue>()
.eq("id", queue.getId())
.eq("time_stamp", queue.getTimeStamp())
.set("status", 1)
.set("time_Stamp", System.currentTimeMillis())
.set("update_time", LocalDateTime.now()));
flag = !queueFlag;
}
处理并不简单,一旦出了问题就需要复杂的调试。经过分析,我们发现,如果数据表中有10条未识别的数据,那么这段代码将会一直无限循环,直到所有的10条数据被更新为已识别。但是,如果每次调用aiQueueService.getOne方法的查询间隔太短,就会触发Mybatis的缓存机制。换句话说,由于短时间内多次查询条件相同,查询的内容并不是从数据表中获取,而是从缓存中获取,这样就会导致代码一直循环下去。
解决方案
在指定的查询方法上添加useCache="false"和flushCache=“true”。这样就不再查询缓存,每次查询都会直接从数据表中获取最新的数据。
<!-- 代码块2 -->
<select id="getOne" resultMap="BaseResultMap" useCache="false" flushCache="true">
select * from ai_queue
</select>
小结
好了下面让我们进一步补充一下相关的Mybatis缓存机制知识:
Mybatis中的缓存机制是为了提高SQL查询效率而存在的。当我们执行一条SQL语句时,如果查询的结果有改变,需要重新查询数据库获取最新数据,这样就会降低查询速度,影响性能。而Mybatis的缓存机制会将SQL查询的结果缓存到内存中,下次查询时直接从缓存中读取,如果缓存中没有数据或者数据过期,才会重新查询数据库获取最新数据。这样就能提高查询效率并减轻数据库压力。
Mybatis的缓存机制分为一级缓存和二级缓存。一级缓存是SqlSession级别的缓存,开启一级缓存是Mybatis默认的行为。在同一个SqlSession中(读写操作在同一个事务中),多次查询同一个SQL,只会发送一次SQL语句到数据库,查询结果存储在一级缓存中。一级缓存是基于PerpetualCache的本地缓存,即存储在当前SqlSession对象中,当该SqlSession对象关闭或执行了更新操作(例如增删改)后,一级缓存也会被清空。
二级缓存是Mapper级别的缓存。需要手动开启,在Mapper.xml中声明。二级缓存是基于CachingExecutor的本地缓存,即不同的SqlSession对象可以共享同一个Mapper.xml中的缓存,缓存的作用范围为同一个Mapper.xml文件中的所有SQL语句。二级缓存的存储方式可以是内存,也可以是文件以及其他外部存储方式,缓存时间长度也可以自定义。
需要注意的是,在使用多表关联查询的情况下,Mybatis缓存会出现许多意想不到的问题,例如多表关联查询数据修改后缓存的不一致等问题。如果遇到此类问题,我们需要根据实际情况来选择开启或者关闭Mybatis缓存机制,并且明确不同场景下的各种缓存的作用范围及实现方式。
通过这种特殊的技术处理可以避免程序出现这种奇怪的Bug。如此简单又有效的解决方案,在我的项目中发挥了非常重要的作用,希望这篇文章能够帮助各位同学们。谢谢阅读~