MyBatis?原生二級(jí)緩存"難以修復(fù)"的原因解析及解決方案
?? 一、為什么 MyBatis 原生二級(jí)緩存“難以修復(fù)”?
MyBatis 二級(jí)緩存的底層設(shè)計(jì)存在結(jié)構(gòu)性缺陷,不是加個(gè)插件就能完美解決的:
| 問(wèn)題 | 原因 | 高并發(fā)影響 |
|---|---|---|
| 緩存 Key 粒度粗糙 | Key = namespace + sql + params,但 params 是對(duì)象哈希,分頁(yè)/排序等參數(shù)容易哈希沖突或忽略語(yǔ)義差異 | 返回錯(cuò)誤數(shù)據(jù)(如分頁(yè)錯(cuò)亂) |
| 無(wú) TTL / 過(guò)期機(jī)制 | 默認(rèn)使用 PerpetualCache,數(shù)據(jù)永不過(guò)期 | 內(nèi)存泄漏 + 臟讀 |
| 跨 JVM 無(wú)法共享 | 緩存是本地內(nèi)存(JVM 內(nèi)),多實(shí)例部署時(shí)各節(jié)點(diǎn)緩存不一致 | 數(shù)據(jù)分裂,用戶看到不同結(jié)果 |
| 寫操作不自動(dòng)失效關(guān)聯(lián)緩存 | 更新 User 表,不會(huì)自動(dòng)清理 OrderMapper 中關(guān)聯(lián)的 user_orders 緩存 | 臟讀持續(xù)存在 |
?? 這些是架構(gòu)級(jí)缺陷,靠攔截器或裝飾器插件無(wú)法根本解決。
?? 二、社區(qū)增強(qiáng)插件方案(可選,但需謹(jǐn)慎)
雖然不能“修復(fù)”,但有插件替換緩存實(shí)現(xiàn),提升可用性:
1.mybatis-redis(最常用)
- GitHub: https://github.com/mybatis/redis-cache
- 原理:用 Redis 作為二級(jí)緩存的存儲(chǔ)后端,解決多節(jié)點(diǎn)一致性問(wèn)題
- 使用方式:
@Mapper
@CacheNamespace(implementation = RedisCache.class)
public interface OrderMapper {
// ...
}
- ? 優(yōu)點(diǎn):跨實(shí)例一致、可設(shè) TTL(需自定義)
- ? 缺點(diǎn):
- 仍無(wú)法解決 Key 語(yǔ)義問(wèn)題(分頁(yè)/動(dòng)態(tài) SQL 緩存 key 仍可能沖突)
- 每次查詢都多一次 Redis 網(wǎng)絡(luò) IO,在 10w QPS 下可能成為瓶頸
- 序列化成本高(Java 對(duì)象需序列化為 byte[])
?? 適用場(chǎng)景:讀多寫少、數(shù)據(jù)變更不頻繁、對(duì)延遲不敏感的配置類數(shù)據(jù)(如字典表)
2.自定義 Cache 實(shí)現(xiàn)(繼承 MyBatisCache接口)
你可以自己實(shí)現(xiàn)帶版本號(hào)或邏輯過(guò)期的緩存:
public class VersionedCache implements Cache {
private final String id;
private final RedisTemplate redis;
@Override
public void putObject(Object key, Object value) {
// key = "order:123", value = { data: {...}, version: 20251203 }
redis.opsForHash().put("mybatis:cache:" + id, serialize(key), wrapWithVersion(value));
}
@Override
public Object getObject(Object key) {
Object cached = redis.opsForHash().get("mybatis:cache:" + id, serialize(key));
if (cached != null && isVersionValid(cached)) {
return unwrap(cached);
}
return null;
}
}- ? 優(yōu)點(diǎn):可控制緩存結(jié)構(gòu)、加版本、加 TTL
- ? 缺點(diǎn):開發(fā)維護(hù)成本高,且仍受限于 MyBatis 緩存 key 生成邏輯
?? 三、我們的選擇:棄用二級(jí)緩存,自建業(yè)務(wù)緩存層
在高并發(fā)訂單系統(tǒng)中,我們最終徹底關(guān)閉 MyBatis 二級(jí)緩存,原因如下:
- 控制權(quán)不足:MyBatis 緩存是“黑盒”,無(wú)法插入降級(jí)、熔斷、監(jiān)控邏輯
- 異常難追溯:緩存臟數(shù)據(jù)問(wèn)題往往延遲暴露,修復(fù)成本高
- 違背“可觀測(cè)性”原則:緩存命中/失效無(wú)法對(duì)接 Prometheus / SkyWalking
? 替代方案:業(yè)務(wù)層 + Redis + 冪等 + 版本號(hào)
@Service
public class OrderService {
public Order getOrder(Long id) {
String cacheKey = "order:v2:" + id;
Order order = redis.get(cacheKey, Order.class);
if (order != null) return order;
// 雙檢鎖 + 空值緩存防穿透
synchronized (getLockKey(id)) {
order = redis.get(cacheKey, Order.class);
if (order == null) {
order = orderMapper.findById(id);
if (order != null) {
redis.setex(cacheKey, 300, order); // 5分鐘過(guò)期
} else {
redis.setex("empty:" + cacheKey, 60, "1"); // 防緩存穿透
}
}
}
return order;
}
@Transactional
public void updateOrder(Order order) {
orderMapper.update(order);
// 主動(dòng)失效緩存(旁路刪除)
redis.delete("order:v2:" + order.getId());
}
}? 優(yōu)勢(shì):
- 緩存 key 語(yǔ)義清晰(含版本 v2,便于灰度/回滾)
- 支持 TTL、空值緩存、主動(dòng)失效
- 可集成 緩存命中率監(jiān)控、慢查詢告警
- 與 Flink/Canal 數(shù)據(jù)對(duì)齊 機(jī)制無(wú)縫銜接
?? 總結(jié):二級(jí)緩存不是“能不能修”,而是“值不值得用”
| 方案 | 適合場(chǎng)景 | 高并發(fā)推薦度 |
|---|---|---|
| MyBatis 原生二級(jí)緩存 | 單機(jī)、低頻、只讀數(shù)據(jù) | ? 不推薦 |
| mybatis-redis 插件 | 多實(shí)例、讀多寫少、容忍一定延遲 | ?? 謹(jǐn)慎評(píng)估 |
| 自定義 Cache 實(shí)現(xiàn) | 有強(qiáng)緩存治理能力的團(tuán)隊(duì) | ?? 成本高 |
| 業(yè)務(wù)層自建緩存 | 高并發(fā)、強(qiáng)一致性、可觀測(cè)性要求高 | ? 強(qiáng)烈推薦 |
最終口訣更新:
“二級(jí)緩存看似香,架構(gòu)缺陷難躲藏;
分頁(yè)排序易沖突,多機(jī)部署更遭殃;
插件替換治標(biāo)難,自建緩存才穩(wěn)當(dāng);
Key 帶版本加 TTL,高并發(fā)下不慌張!”
如果你正在設(shè)計(jì)高并發(fā)系統(tǒng),放棄對(duì) MyBatis 二級(jí)緩存的幻想,把緩存控制權(quán)拿回業(yè)務(wù)層,才是真正的“韌性設(shè)計(jì)”。
到此這篇關(guān)于MyBatis 原生二級(jí)緩存“難以修復(fù)”的原因解析及解決方案的文章就介紹到這了,更多相關(guān)mybatis二級(jí)緩存修復(fù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- mybatis中的延遲加載和一二級(jí)緩存深入解析
- mybatisplus 配置二級(jí)緩存及實(shí)戰(zhàn)示例
- MyBatis 二級(jí)緩存實(shí)現(xiàn)機(jī)制的示例解析
- 淺析SpringBoot整合Mybatis如何實(shí)現(xiàn)二級(jí)緩存
- MyBatis中一級(jí)緩存和二級(jí)緩存的區(qū)別
- 一文詳解mybatis二級(jí)緩存執(zhí)行流程
- springboot開啟mybatis二級(jí)緩存的步驟詳解
- Mybatis-plus如何開啟二級(jí)緩存
- MyBatis Plus整合Redis實(shí)現(xiàn)分布式二級(jí)緩存的問(wèn)題
相關(guān)文章
Java中ByteBuddy動(dòng)態(tài)字節(jié)碼操作庫(kù)的使用技術(shù)指南
ByteBuddy?是一個(gè)功能強(qiáng)大的?Java?字節(jié)碼操作庫(kù),可以幫助開發(fā)者在運(yùn)行時(shí)動(dòng)態(tài)生成和修改類,而無(wú)需直接接觸復(fù)雜的?ASM?API,本文給大家介紹了Java?ByteBuddy動(dòng)態(tài)字節(jié)碼操作庫(kù)的使用技術(shù)指南,需要的朋友可以參考下2025-04-04
Java 數(shù)據(jù)庫(kù)連接(JDBC)的相關(guān)總結(jié)
這篇文章主要介紹了Java 數(shù)據(jù)庫(kù)連接(JDBC)的相關(guān)總結(jié),幫助大家更好的理解和學(xué)習(xí)使用Java,感興趣的朋友可以了解下2021-03-03
Spring時(shí)間戳(日期)格式轉(zhuǎn)換方式
這篇文章主要介紹了Spring時(shí)間戳(日期)格式轉(zhuǎn)換方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-03-03
Spring Boot 整合持久層之JdbcTemplate
持久層是 Java EE 中訪問(wèn)數(shù)據(jù)庫(kù)的核心操作,Spring Boot 中對(duì)常見(jiàn)的持久層框架都提供了自動(dòng)化配置,例如 JdbcTemplate 、 JPA 等,Mybatis 的自動(dòng)化配置則是 Mybatis 官方提供的2022-08-08
java代碼實(shí)現(xiàn)銀行管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了java代碼實(shí)現(xiàn)銀行管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-12-12
Springboot集成Proguard生成混淆jar包方式
本文介紹了兩種Java代碼混淆工具:ClassFinal和ProGuard,ClassFinal是一個(gè)字節(jié)碼加密工具,但需要額外的加密包,使用復(fù)雜,ProGuard是一款開源的Java代碼混淆工具,可以有效地提高代碼的安全性,但對(duì)Spring框架的注解處理不夠完善2024-11-11
Java中JSON字符串與java對(duì)象的互換實(shí)例詳解
這篇文章主要介紹了在java中,JSON字符串與java對(duì)象的相互轉(zhuǎn)換實(shí)例詳解,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-08-08
Java構(gòu)造函數(shù)里的一些坑記錄super()和this()
這篇文章主要介紹了Java構(gòu)造函數(shù)里的一些坑記錄super()和this(),具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-03-03

