mybatis二级缓存脏读的根本原因是缓存隔离粒度(namespace)与数据更新范围(跨表共享)不匹配,导致跨mapper更新时无法自动失效关联缓存,且无跨namespace通知机制、一级缓存不参与同步、定时刷新策略不满足强一致性需求。

MyBatis 二级缓存产生脏读,根本原因在于缓存隔离粒度与数据更新范围不匹配,尤其在多表关联查询+跨 Mapper 更新的场景下,缓存无法感知“非本 namespace”的数据变更。
缓存以 Namespace 为单位隔离,但数据是跨表共享的
二级缓存按 Mapper 的 namespace 划分——每个 XML 文件或 @CacheNamespace 接口拥有独立缓存实例。例如:
-
DeptDao.xml(namespace="DeptDao")执行
SELECT dept + deptNum,结果缓存在 DeptDao 的二级缓存中; -
DeptNumDao.xml(namespace="DeptNumDao")执行
UPDATE deptNum,只会清空 DeptNumDao 自己的缓存,对 DeptDao 缓存毫无影响。
虽然 deptNum 表被更新了,但 DeptDao 缓存里仍躺着旧的“ali 部门人数=1050”,后续查询直接返回该过期值,形成脏读。
更新操作不会自动传播到其他 namespace 的缓存
MyBatis 的缓存清空机制是单 namespace 局部生效的:
- 执行
<update namespace="DeptNumDao"></update>→ 仅触发 DeptNumDao 缓存 flush; - DeptDao 缓存完全无感知,也不会监听 deptNum 表变化;
- 没有跨 namespace 的缓存失效通知机制,也没有数据库 binlog 或表级监听能力。
一级缓存不参与跨会话同步,加剧脏读暴露
即使某次查询命中了一级缓存(SqlSession 内),只要该 SqlSession 没 commit,它的一级缓存数据也永远不会写入二级缓存**。而真正从二级缓存读取数据的,往往是另一个 SqlSession —— 它拿到的是上次 commit 后残留的、早已过期的二级缓存快照。
默认刷新策略无法覆盖业务语义一致性
比如配置了 flushInterval="60000",看似每分钟刷新一次,但问题在于:
- 刷新是全量清空,不是按 key 精准失效;
- 更新发生在第 3 秒,缓存却要等到第 60 秒才清,中间 57 秒全是脏数据;
- 业务上“部门人数变了”和“部门详情页应立刻刷新”之间,存在强一致性诉求,定时刷新无法满足。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











