MyBatis一级缓存默认开启、绑定SqlSession,二级缓存需全局配置+Mapper声明+实体序列化;二者均易引发脏读,尤其在多会话、高并发或关联查询场景下。

MyBatis 的一级缓存默认开启、无需配置,二级缓存需显式启用且有多个前提条件。用错或忽略细节容易引发脏读、数据不一致等问题,尤其在多会话、高并发或关联查询场景下。
一级缓存:默认生效,但要注意它的“边界”
一级缓存绑定 SqlSession 实例,本质是 BaseExecutor 内部的 PerpetualCache(底层为 HashMap),同一 SqlSession 中重复执行完全相同的 select 语句会直接命中缓存。
- 缓存 Key 由五部分组成:Mapper 全限定名 + SQL 语句 + 参数值 + 分页 offset/limit + 环境 ID —— 任一变化都会导致未命中
- 只要执行过 insert/update/delete(哪怕操作的是其他表),当前 SqlSession 的一级缓存立即清空
- 调用 sqlSession.clearCache() 或 sqlSession.close() / commit() / rollback() 都会销毁缓存
- 注意线程安全:SqlSession 不是线程安全的,通常靠 Spring 的 SqlSessionTemplate 或 ThreadLocal 管理,避免跨线程共享同一个 SqlSession
二级缓存:三步启用,缺一不可
二级缓存作用域是 Mapper 的 namespace,跨 SqlSession 共享,但必须满足三个硬性条件才能真正生效:
-
全局开关打开:在 mybatis-config.xml 或 application.yml 中启用
或 Spring Boot 中:
mybatis: configuration: cache-enabled: true -
Mapper XML 中声明
:放在 mapper 标签内部,可配置参数如 eviction、size、flushInterval、readOnly - 查询返回的实体类实现 Serializable 接口:否则启动时抛异常,因为二级缓存需序列化存储(即使是本地缓存)
常见坑点与避坑建议
二级缓存看似简单,但实际踩坑率极高,核心问题集中在数据一致性上:
- 写操作自动清空缓存,但仅限本 Mapper namespace:比如 ProductMapper 执行了 update,只会清 ProductMapper 的缓存,不会影响 OrderMapper 中关联查出的 product 数据 → 建议对关联查询统一使用 cache-ref 或禁用二级缓存
- readOnly="false" 要慎用:设为 false 时 MyBatis 返回缓存对象的拷贝,但深拷贝逻辑依赖 Serializable,若对象含非序列化字段或循环引用,可能失败或性能陡降;推荐设为 true,返回只读对象,性能更好
- 不要缓存实时性要求高的数据:如用户余额、订单状态、库存数量等,应走数据库直查或搭配 Redis 做分布式缓存+主动失效
- Spring 事务内多次查询可能“看似没走缓存”:因事务中 SqlSession 通常复用,一级缓存仍有效;但二级缓存只有在事务提交后才写入,未提交前其他会话查不到最新结果
什么时候该关掉缓存?
不是所有场景都适合开缓存。以下情况建议关闭二级缓存,或一级缓存也谨慎使用:
- 高频更新的表(如日志表、消息表、订单明细)
- 涉及复杂动态 SQL(where 条件组合极多,缓存命中率趋近于 0)
- 单次查询耗时极短(
- 微服务集群部署且未集成 Redis/EhCache 等分布式缓存 → 各节点二级缓存不同步,必然数据不一致











