---
### **一、一级缓存的核心特性**
1. **作用范围**
一级缓存是 **SqlSession 级别**的缓存,仅在同一个数据库会话(SqlSession)内部有效。
• **示例**:
```java
SqlSession session1 = sqlSessionFactory.openSession();
User user1 = session1.selectOne("getUserById", 1); // 首次查询,写入缓存
User user2 = session1.selectOne("getUserById", 1); // 命中缓存
```
此时 `user1` 和 `user2` 是同一个对象,说明缓存生效。
2. **生命周期**
缓存数据在以下情况会被清空:
• SqlSession 关闭(`session.close()`)
• 执行增删改操作(INSERT/UPDATE/DELETE)
• 显式调用 `session.clearCache()`。
---
### **二、脏数据问题的根源**
1. **多 SqlSession 场景**
当存在多个 SqlSession 时,**每个会话的缓存相互独立**。例如:
```java
SqlSession session1 = sqlSessionFactory.openSession();
SqlSession session2 = sqlSessionFactory.openSession();
User user1 = session1.selectOne("getUserById", 1); // 查询并缓存
session2.update("updateUser", new User(1, "新名字")); // 另一个会话更新数据
User user2 = session1.selectOne("getUserById", 1); // 仍读取旧缓存
```
此时 `user2` 获取的是未更新的旧数据,导致脏读。
2. **分布式环境风险**
在分布式系统中,不同节点的 SqlSession 完全隔离。若节点 A 更新数据,节点 B 的缓存无法感知变化,仍返回旧数据。
---
### **三、解决方案:设定缓存级别为 STATEMENT**
1. **配置方法**
在 MyBatis 全局配置中修改 `localCacheScope` 参数:
```xml
<settings>
<setting name="localCacheScope" value="STATEMENT"/>
</settings>
```
• `SESSION`(默认):启用一级缓存
• `STATEMENT`:关闭一级缓存,每次查询后清空缓存。
2. **底层原理**
当设置为 `STATEMENT` 时,MyBatis 会在 **每次执行 SQL 后立即清空缓存**,确保每次查询都直接访问数据库,避免缓存与数据库数据不一致。
---
### **四、实际场景中的权衡**
1. **何时保留一级缓存**
• 单线程、短生命周期的简单查询(如管理系统中的静态数据展示)
• 对性能要求高且数据更新频率极低的场景。
2. **何时禁用一级缓存**
• 高并发或分布式环境
• 需要强数据一致性的业务(如金融交易系统)
• 多表关联查询且关联表可能被其他会话修改的场景。
---
### **五、补充:与 Spring 整合时的特殊情况**
当 MyBatis 与 Spring 整合时:
• **默认未开启事务**:每次数据库操作会创建新的 SqlSession,一级缓存实际无效。
• **开启事务**:同一个事务内复用 SqlSession,此时一级缓存生效,需特别注意脏数据风险。
---
### **总结**
• **问题本质**:一级缓存的隔离性导致多会话或分布式环境下数据更新不可见。
• **核心建议**:在需要强一致性的场景中,通过 `localCacheScope=STATEMENT` 禁用一级缓存,或改用二级缓存(需注意二级缓存的多表关联问题)。
• **扩展方案**:分布式环境下可集成 Redis 等集中式缓存,但需处理缓存穿透、雪崩等问题。
- 请尽量让自己的回复能够对别人有帮助
- 支持 Markdown 格式, **粗体**、~~删除线~~、
`单行代码`
- 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
- 图片支持拖拽、截图粘贴等方式上传