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