957 字
约 3 分钟
7
S08-J03 · 缓存:一级/二级与失效
适用读者:以为"开了二级缓存就一定快"的人。
前置:S08-B01。
覆盖旧文:897(86) 902(74) + 空壳 1144 补写(E-S08-013)= 160 点。
上一篇:S08-J02 · 插件与拦截器链
下一篇:S08-J04 · 批量与注入器。
1. 一句话结论
一级缓存是 SqlSession 级(默认开),二级缓存是 Mapper 级(需手动开)。它们的失效条件与"能不能用"都取决于事务与更新动作。
| 级别 | 作用域 | 默认 | 共享 |
|---|---|---|---|
| 一级 | 同一个 SqlSession |
开启 | ❌ |
| 二级 | 同一个 namespace(Mapper) | 关闭(需 <cache/>) |
✅(同 JVM) |
Spring 环境下的关键事实:SqlSessionTemplate 每次操作都会关闭 SqlSession(除非在事务内),所以一级缓存"看起来没用"——因为它只在同一事务/会话内有效。
2. 一级缓存的失效条件
| 动作 | 是否清空一级缓存 |
|---|---|
| 同一 SqlSession 内再次相同查询 | ✅ 命中 |
执行 insert/update/delete |
清空 |
sqlSession.clearCache() |
清空 |
| SqlSession 关闭(Spring 下每次操作后) | 缓存没了 |
判据:"这两次查询在同一个事务里吗?" 不在 → 一级缓存不生效。
3. 二级缓存的三个硬约束
| 约束 | 原因 |
|---|---|
必须 implements Serializable |
缓存对象可能被序列化 |
| 必须在同一个 namespace | 缓存 key 含 statementId |
| 跨表更新不会自动清空 | 只能清自己 namespace 的缓存 |
最后一条是最大的坑:A 表更新不会清 B 表的二级缓存 → 脏读。
| 场景 | 二级缓存 |
|---|---|
| 单表、读多写少、可容忍轻微不一致 | 可用 |
| 多表关联查询 | 别开(更新无法联动清理) |
| 分布式/多实例 | 别开(各 JVM 独立缓存) |
4. 延迟加载(A-902)
| 配置 | 行为 |
|---|---|
lazyLoadingEnabled=true |
关联对象按需加载 |
aggressiveLazyLoading=false |
不用到就不加载 |
原理:返回代理对象,访问属性时才发第二条 SQL。
坑:延迟加载在事务外访问会抛 LazyInitializationException(与 JPA 同款问题)——接口返回实体时最容易踩。
5. 实践建议
| 建议 | 理由 |
|---|---|
| 优先用 Redis 等外部缓存 | 跨实例、可控失效 |
| MyBatis 缓存只做"会话内优化" | 一级缓存天然如此 |
| 二级缓存慎用 | 失效难控 |
| 缓存对象用 DTO | 避免序列化与懒加载问题 |
6. 五个误区
| 误区 | 真相 |
|---|---|
| "一级缓存默认全局生效" | 只在同一 SqlSession/事务内 |
| "开了二级缓存就快" | 失效问题可能带来脏数据 |
| "更新会自动清所有缓存" | 只清当前 namespace |
| "延迟加载总是更好" | 事务外访问会抛异常 |
| "MyBatis 缓存能替代 Redis" | 不跨实例 |
7. 自检
- 一级缓存的作用域与失效条件?
- Spring 下为什么"感觉一级缓存没用"?
- 二级缓存的三条硬约束?
- 跨表更新对二级缓存有什么影响?
- 延迟加载在什么场景会抛异常?
8. 下一篇
S08-J04 · 批量操作与 SQL 注入器
缓存讲完,进阶层最后一篇:批量插入的三种写法与性能差异,以及"SQL 注入器"这个 MP 扩展点。
本篇属于系列《S08 · MyBatis 持久层》。旧文来源:A-897/902(空壳 1144 补写)。
S08-J03 · 缓存:一级/二级与失效
https://www.clxhxhhr.top/posts/5289/ 评论
0 条
还没有评论,先写一条吧。