MyBatis 一级缓存和二级缓存分别是什么?使用中有哪些坑?
结论先行:一级缓存是 SqlSession 级别的本地缓存(默认开启):同一 SqlSession 内、相同 SQL 加参数的查询直接命中缓存,会话提交、回滚、执行更新或关闭时缓存清空。二级缓存是 namespace(Mapper)级别的共享缓存(默认关闭,需配置 cache 标签):可跨 SqlSession 命中同一 Mapper 的结果。缓存最大的坑永远是“与数据不一致”,多表、多实例场景尤其要小心。
对比表
| 维度 | 一级缓存 | 二级缓存 |
|---|---|---|
| 默认状态 | 开启 | 关闭 |
| 作用范围 | 单个 SqlSession | namespace(Mapper) |
| 共享性 | 会话内独占 | 跨 SqlSession 共享 |
| 失效时机 | 提交/回滚/更新/关闭会话 | 该 namespace 执行更新(flushCache) |
| 常见问题 | 长会话读到旧数据 | 多表 join 产生缓存脏数据 |
典型坑点
- 多表 join:结果缓存进 A Mapper,另一事务更新 B 表不会触发 A 的缓存刷新 → 读到脏数据;
- 二级缓存命中后不再执行 SQL:依赖自增主键回填、触发器、序列等数据库副作用的逻辑会出错;
- 集群多实例各自持有本地缓存:一致性无法保证,需用 Redis 等外部缓存替代;
- 一级缓存使同一 SqlSession 内重复查询结果“冻结”,配合长会话/复用会话要谨慎。
配置示例
<mapper namespace="com.example.UserMapper">
<!-- eviction 淘汰策略 / flushInterval 刷新间隔 / size 缓存上限 -->
<cache eviction="LRU" flushInterval="60000" size="512" readOnly="false"/>
<select id="findById" resultType="User" useCache="true">
SELECT * FROM user WHERE id = #{id}
</select>
</mapper>
常见追问 / 记忆点
- 追问:为什么生产不推荐开二级缓存?答:多表、多实例下一致性难以保证,业界普遍用 Redis 等外部缓存替代读缓存需求。
- 追问:一级缓存什么时候清空?答:执行 insert/update/delete(无论是否提交)、commit、rollback、close 或显式 clearCache 时。
- 记忆点:一缓存“会话内”,二缓存“Mapper 内”;缓存最大的坑永远叫数据不一致。