MyBatis 缓存面试常见问法是:"一级缓存和二级缓存用过吗?"不要只背"一级缓存是 SqlSession 级别,二级缓存是 namespace 级别"。更好的回答是讲清楚 缓存作用域、什么时候命中、什么时候清空、为什么二级缓存要谨慎使用 。

命中
未命中
是
命中
未命中
否
Mapper 查询
先查一级缓存
直接返回结果
是否开启
二级缓存
查询 namespace
二级缓存
查询数据库
写入一级缓存
SqlSession 关闭或提交后
写入二级缓存
一级缓存是什么
一级缓存是 MyBatis 默认开启的本地缓存,作用域是 SqlSession。
它底层基于 PerpetualCache,本质上可以理解成一个 HashMap。
同一个 SqlSession 中,执行完全相同的查询,第一次查数据库,第二次可能直接命中缓存。
java
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper1 = sqlSession.getMapper(UserMapper.class);
UserMapper mapper2 = sqlSession.getMapper(UserMapper.class);
User user1 = mapper1.selectById(6);
User user2 = mapper2.selectById(6);
sqlSession.close();
如果中间没有刷新缓存,两个查询在同一个 SqlSession 内,通常只会执行一次 SQL。
一级缓存会在这些情况下清空:
SqlSession执行insert、update、delete。- 手动执行
clearCache()。 SqlSession关闭。- 查询配置要求刷新缓存。
二级缓存是什么
二级缓存的作用域是 namespace,也就是 Mapper 级别。它不依赖单个 SqlSession,不同 SqlSession 之间可以共享。
java
SqlSession sqlSession1 = sqlSessionFactory.openSession();
UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
User user1 = mapper1.selectById(6);
sqlSession1.close();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
User user2 = mapper2.selectById(6);
sqlSession2.close();
如果二级缓存开启,并且第一次会话关闭后数据进入二级缓存,第二个会话查询相同数据时就可能命中缓存。
二级缓存默认不开启,需要两步:
xml
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
在 Mapper 映射文件中开启:
xml
<mapper namespace="com.example.UserMapper">
<cache/>
</mapper>
同时,二级缓存中的对象通常需要实现 Serializable 接口。
一级缓存和二级缓存对比
| 维度 | 一级缓存 | 二级缓存 |
|---|---|---|
| 作用域 | SqlSession |
namespace / Mapper |
| 是否默认开启 | 默认开启 | 默认不开启 |
| 是否跨会话 | 不跨会话 | 可以跨会话 |
| 底层实现 | PerpetualCache |
默认也是 PerpetualCache |
| 清理时机 | session flush、更新、关闭 | 当前 namespace 发生增删改后清理 |
| 使用建议 | 默认即可 | 谨慎使用,关注数据一致性 |
二级缓存什么时候清理
缓存最大的问题不是能不能命中,而是数据什么时候失效。

否
是
执行 select 查询
结果进入缓存
后续相同查询
命中缓存
同作用域是否
发生写操作
继续复用缓存
insert
update
delete
清空该作用域缓存
下一次查询
重新访问数据库
MyBatis 默认规则是:
某个作用域发生新增、修改、删除操作后,该作用域下相关 select 缓存会被清空。
比如 UserMapper namespace 下执行了 updateUser,那么 UserMapper 这个 namespace 下的二级缓存会被清理,避免后续读到旧数据。
但这也带来一个问题:如果多个 Mapper 操作的是同一张表,而二级缓存按 namespace 隔离,缓存一致性就容易变复杂。
为什么二级缓存要谨慎
二级缓存看起来很美好,但实际项目中要谨慎:
- 数据更新频繁时,缓存很容易被频繁清空,收益不大。
- 多个 namespace 操作同一批数据时,缓存一致性不好控制。
- 分布式部署下,本地二级缓存无法天然跨节点一致。
- 业务已经使用 Redis 这类集中式缓存时,MyBatis 二级缓存容易和业务缓存叠加出复杂问题。
所以真实项目里,MyBatis 一级缓存默认使用即可;跨请求、跨服务的热点数据缓存,通常会交给 Redis 这类专门缓存组件。
面试回答模板
可以这样回答:
MyBatis 一级缓存是
SqlSession级别的本地缓存,默认开启,底层基于PerpetualCache,可以理解成 HashMap。同一个SqlSession中相同查询可能命中一级缓存;当 session 发生增删改、flush、clear 或 close 时缓存会清空。二级缓存是 namespace 或 Mapper 级别的缓存,默认关闭,需要全局开启cacheEnabled,并在 Mapper 中配置<cache/>。二级缓存可以跨SqlSession,但只有会话提交或关闭后,一级缓存中的数据才会进入二级缓存。
如果继续问清理机制,可以补:
当某个作用域发生新增、修改、删除操作后,MyBatis 默认会清空该作用域下 select 缓存,避免读到旧数据。但二级缓存按 namespace 隔离,多 Mapper、多服务场景下数据一致性比较难控制,所以实际项目中要谨慎使用。
小结
MyBatis 缓存要抓住两个词:作用域 和 失效。
一级缓存解决的是同一个 SqlSession 内重复查询;二级缓存解决的是同一个 Mapper namespace 下跨会话复用结果。缓存能不能用,不只看能不能命中,更要看数据更新后能不能及时失效。