MyBatis 一级缓存与二级缓存的原理是什么?有哪些使用坑?

结论先行:MyBatis 一级缓存是 SqlSession 作用域的本地 Map,默认开启,同一会话内相同查询命中缓存;二级缓存是 Mapper(namespace) 作用域、跨 SqlSession 共享,需显式开启并自行承担一致性风险。一级缓存“同查询同结果”看似无害,却常与 Spring 事务、跨会话场景叠加出脏读/陈旧数据问题。

一、两级缓存对比

维度一级缓存(Local Cache)二级缓存
作用域SqlSession(默认 SESSION)namespace(跨 SqlSession)
默认状态开启关闭,需 <cache/> 开启
存储SqlSession 内 Map全局缓存(可配 Redis/ehcache)
失效会话提交/关闭/clearCache、更新语句namespace 内增删改清空该缓存
风险事务内读到本会话旧值跨 namespace 脏数据、集群不一致

二、一级缓存的关键机制

  1. 缓存键 = statementId + SQL + 参数 + RowBounds;
  2. 查询先查缓存,未命中才查库并回填;
  3. 同 SqlSession 内执行 update/insert/delete 会清空缓存(避免脏读);
  4. 坑点一:Spring 集成时若未开启事务,每次 Mapper 调用新建 SqlSession,一级缓存形同虚设;
  5. 坑点二:开启事务时整个事务共用一个 SqlSession,先查后改可能读到“改前”的旧结果(可调 localCacheScope=STATEMENT 或强制刷新)。

三、二级缓存使用与坑

<mapper namespace="com.demo.mapper.UserMapper">
  <cache eviction="LRU" flushInterval="60000" size="1024" readOnly="false"/>
  <!-- 关联表更新语句上补 flushCache="true",防止跨表脏读 -->
  <update id="updateOrder" flushCache="true">...</update>
</mapper>
  • 共享缓存的对象需可序列化(readOnly=false 时);
  • 经典坑:多表 join 或别处更新关联表,只清了本 namespace 缓存 → 读到旧数据,需 cache-ref 共享或合理拆分;
  • 集群/多实例:本地二级缓存不共享,必须接分布式缓存(如 mybatis-redis)或干脆关闭;
  • 与事务配合:提交后才把数据放入二级缓存,回滚不会污染缓存。

常见追问 / 记忆点

  • 追问:一级缓存会不会读到已提交的其他会话数据?答:不会跨会话,一级缓存只对本 SqlSession 可见,天然无跨会话问题。
  • 追问:线上要不要开二级缓存?答:单机低写场景收益有限、一致性风险高;高并发读多写少且有分布式方案才考虑。
  • 记忆点:一级=会话内、自动开;二级=namespace、手动开;集群一致性是二级缓存最大敌人。
笔记加载中…