记一次MyBatis缓存的问题 - ZhangTory's NoteBlog - 张耀誉的笔记博客

记一次MyBatis缓存的问题

之前我只知道MyBatis在重复查询查询相同SQL时,为了避免直接对数据库进行查询,提高性能,所以加入了缓存机制,但是一直都没有太注意这个问题。
直到最近在工作中发现一个问题:在一个事务中多次查询Oracle的sequence时,竟然返回了相同的值!

发现这个问题是因为一个主订单会存在多个商品,在写入订单详情的时候,就需要创建多个订单详情,因为我们使用的是Oracle,所以创建了一个sequence用于生成订单详情单号。
众(ni)所(bu)周(zhi)知(dao),这一块的内容都是前人写的,之前一直是在他的基础上进行一些修改。最近有个需求比较独立,所以就单独做,不过收单这一块的流程还是大致保持了原来的逻辑,包括这次的主角,订单详情单号。
订单详情需要对每个商品创建一条记录,大概是这样的:

for (Goods goods : goodsList) {
    Long id = SeqMapper.getSeq();
    XXXXX
}
<select id="getSeq" resultType="java.lang.Long">
        select SEQ_TEST_ID.nextval from dual
</select>

收单流程涉及到许多SQL,我们需要保证它们的原子性,所以必须使用事务。
然而问题来了,测试的时候发现报主键重复的错误。
debug看了看,SeqMapper.getSeq()时确实每次返回的值都是一样,为什么呢?
难道是在一次事务中取的sequence值都一样?
于是我尝试性的修改了一下事务的传播性,增加了一个方法:

@Transactional(propagation = Propagation.REQUIRES_NEW)
public String getSeqNo() {
    return SeqMapper.getSeq();
}

因为循环调用时每次都新开了一个事务,所以就能够正确取到不同的sequence值了。
虽然这样从功能来说是没有问题了,但是自觉告诉我肯定不是这样的,因为sequence是原子性的,即使是在一个事务中也不可能nextval时返回相同的值。
并且,为什么之前的代码没有问题?

于是我认真看了下以前的代码,发现了一个不同之处。
之前的老哥在SeqMapper.getSeq()后,马上就构建好数据insert了一次,而我是SeqMapper.getSeq()并构建好数据后,先存入了list,之后一并insert。

为了确定是这个原因,我做了一个测试,在SeqMapper.getSeq()后分别做了insert/update/select操作,发现加入insert/update都可以使SeqMapper.getSeq()获取到最新的值。

到这里我恍然大悟,肯定是因为MyBatis的缓存机制的原因。
因为insert/update这类的操作会删除掉缓存,所以下一次SeqMapper.getSeq()一定就是新值了。
而select或者不做任何操作,SeqMapper.getSeq()则是从MyBatis的缓存中获取的值,不会去数据库执行操作,所以就不会取到新值。

为什么我能想到这里呢?是因为MySQL自己也有缓存,你连续两次相同的select会发现第二次比第一次快,就是因为MySQL也有缓存,而当你对表做insert/update/delete之类的操作时MySQL防止脏读就会删除缓存。

所以我们就可以确定是MyBatis的缓存问题了,解决也很简单:flushCache="true"

<select id="getSeq" resultType="java.lang.Long" flushCache="true">
    select SEQ_TEST_ID.nextval from dual
</select>

每次刷新缓存就可以了。

这次解决问题还是很轻松的,几分钟时间就定位解决了问题,不过因为MyBatis缓存之前接触的很少,所以就需要总结总结。

最后还是得补习一下MyBatis缓存的基本知识。
MyBatis缓存分为一级缓存和二级缓存,它们的作用域不同。
一级缓存是Sql Session级别,每个会话拥有一个独立的Local Cache,即使是执行不同mapper的sql都可以使用到缓存。
二级缓存是Mapper级别,xml中同一个namespace共享一个cache,即使是在多个Sql Session中,只要是同一个Mapper都能够使用到缓存。

开启缓存后,SQL查询的顺序是:
二级缓存 -> 一级缓存 -> 数据库

添加新评论

电子邮件地址不会被公开,评论内容可能需要管理员审核后显示。