为什么很多公司禁用 MyBatis 二级缓存?看完你就不敢乱开了
先说结论:MyBatis 二级缓存默认并不会生效,必须显式配置 。但即使如此,很多公司仍然选择禁用它,因为缓存一致性问题远比性能问题更难处理。
很多人学 MyBatis 的时候都会看到这样一个功能:
<cache/>
官方文档告诉你:
开启之后,查询结果会缓存,下次直接从缓存读取,减少数据库压力。
听起来是不是很美好?但现实却是:很多公司的 MyBatis 配置里根本不开二级缓存,甚至架构规范明确规定:
禁止使用 MyBatis 二级缓存
为什么?既然官方提供了这个功能。为什么大厂反而不用?
今天我们从源码和实际项目角度聊清楚。
一级缓存和二级缓存到底是什么
很多人先把这两个概念搞混了。
一级缓存
作用范围:
SqlSession
例如:
User user1 = mapper.selectById(1);
User user2 = mapper.selectById(1);
同一个 SqlSession。
只会查询一次数据库,第二次直接走缓存。
缓存位置:
BaseExecutor
源码:
protected PerpetualCache localCache;
结构:
SqlSession
↓
localCache
↓
查询结果
一级缓存默认开启,而且基本没有风险。
二级缓存
作用范围:
Mapper级别
例如:
UserMapper
多个 SqlSession 共享。
配置:
<cache/>
开启后:
SqlSession1
↓
UserMapper Cache
↑
SqlSession2
所有会话共享同一份缓存。
问题也从这里开始。
二级缓存最大的坑:数据不一致
假设:
数据库:
id=1
name=张三
第一次查询:
selectById(1)
结果进入缓存。
缓存:
id=1
name=张三
此时另一个系统修改数据:
update user
set name='李四'
where id=1;
数据库:
李四
缓存:
张三
这时候:
selectById(1)
读到的还是:
张三
脏数据出现了。
为什么互联网项目特别怕这个
假设是用户余额:
数据库
1000
缓存:
1000
扣款后:
数据库
800
缓存:
1000
用户再次查询:
余额1000
直接出事故。
所以涉及:
- 用户余额
- 库存
- 订单
- 权限
这种数据。
根本不允许出现缓存脏读。
MyBatis 根本不知道谁修改了数据库
这是最核心的问题。
很多人觉得:
更新时清缓存不就行了?
理论上没错,实际上做不到。
因为系统越来越大后:
服务A
服务B
服务C
都可能操作同一张表。
例如:
订单服务
营销服务
后台管理系统
数据同步程序
都能更新数据库。
MyBatis 二级缓存只能感知:
自己执行的更新
却不知道:
别人执行的更新
所以缓存很容易失效。
集群环境更加危险
单机还勉强能玩,集群直接出问题。
例如:
服务器A
服务器B
服务器C
每台机器:
自己的JVM
自己的缓存
结构:
A缓存
B缓存
C缓存
互相不知道。
用户访问:
请求1 → A
缓存:
张三
然后:
请求2 → B
数据库已经变成:
李四
结果:
A返回张三
B返回李四
同一个用户,两次查询结果不一样。
二级缓存本质是 JVM 本地缓存
源码:
PerpetualCache
内部结构:
private final Map<Object,Object> cache;
其实就是:
HashMap
没错,就是 JVM 内存里的一个 Map,所以:
无法跨机器同步
天然不适合集群。
事务提交后才写入缓存
很多人不知道这个细节。
查询完成:
selectById()
并不会立刻进入二级缓存。
源码:
TransactionalCache
提交事务:
sqlSession.commit()
才会:
flushPendingEntries()
写入缓存。
原因很简单:避免事务回滚导致缓存脏数据。
看起来很聪明,但效率反而下降
查询流程:
查询
↓
一级缓存
↓
二级缓存
↓
数据库
每次都要检查缓存、每次都要序列化、每次都要维护缓存状态。
如果命中率不高:
缓存收益
<
维护成本
反而变慢。
更致命的问题:对象序列化
开启二级缓存后:
实体通常要求:
implements Serializable
例如:
public class User
implements Serializable {
}
原因:缓存对象可能需要序列化,否则直接报错,很多老项目都踩过这个坑。
现在大家都用什么替代
答案:
Redis
架构变成:
应用
↓
Redis
↓
MySQL
优势:
统一缓存
所有机器共享:
Redis
不存在:
A机器缓存
B机器缓存
问题。
可以主动失效
更新数据:
updateUser();
redis.del(key);
立即失效,比 MyBatis 二级缓存可靠得多。
可以设置过期时间
TTL=30分钟
即使忘记删除,也不会长期脏数据。
支持分布式
Redis 天然支持:
多节点
集群
哨兵
主从
远比本地缓存成熟。
那二级缓存还有价值吗
有。但适用场景非常少。
例如:
基础配置表
省份表
城市表
字典表
菜单配置
特点:
读多写少
几乎不更新
这种场景比较适合。
单体项目
没有集群,没有微服务,只有一台机器,这种情况也能使用。
为什么大厂普遍禁用
真正原因其实只有一句话:
缓存失效比缓存命中难一万倍。
MyBatis 二级缓存:
解决的是:
查询性能
带来的却是:
数据一致性风险
而对于互联网系统来说:
错误的数据
比
慢一点的数据
更可怕
所以大多数公司的选择都是:
一级缓存保留
二级缓存关闭
Redis统一管理缓存
总结
MyBatis 二级缓存并不是没用,而是不适合现代互联网架构。
核心问题:
本地缓存
无法跨服务同步
容易出现脏数据
源码核心类:
PerpetualCache
TransactionalCache
CachingExecutor
缓存流程:
一级缓存(SqlSession)
↓
二级缓存(Mapper)
↓
数据库
所以你会发现:很多公司不是不会用二级缓存,而是了解原理之后,主动选择关闭。
上篇文章:
《MyBatis 插件为什么这么强大?一次看懂拦截器机制》
下篇文章:
《MyBatis 到底解决了什么问题?》



