常见错误写法:先删缓存,再更新db
更新操作时,先删除缓存,再更新db; 查询操作时把db数据加载到缓存。 ✘
by:两个并发操作,一个更新操作,另一个查询操作,更新操作删除缓存后,查询操作没有命中缓存,从db中读取到老数据放到缓存中,然后更新操作更新了db,此时缓存中的数据一直就是老的脏数据!
正确姿势:先更新db,再删缓存
更新操作时,先更新db,再删除缓存; 查询操作时把db数据加载到缓存。 ✔
不完美的地方
并发问题:一个是查询操作,但是没有命中缓存,然后就到db中取数据,此时来了一个更新操作,更新完db后,让缓存失效,然后,之前的那个查询操作再把老的数据放进去,所以,会造成脏数据。
但,这个case理论上会出现,不过,实际上出现的概率可能非常低,因为这个条件需要发生在读缓存时缓存失效,而且并发着有一个写操作。而实际上数据库的写操作会比读操作慢得多,而且还要锁表,而读操作必需在写操作前进入数据库操作,而又要晚于写操作更新缓存,所有的这些条件都具备的概率基本并不大!
仔细想想ing
追求最终一致性:延迟双删策略
对上述两种写法产生的脏数据进行处理,达到最终一致性!(强一致性达不到,缓存适用的场景就是非强一致性场景)
有待学习实践... 缓存与数据库一致性问题深度剖析
号外:为什么不更新完db后直接更新缓存?
因为更新db是一个写操作,更新缓存也是一个写操作,并发的两个写操作会导致脏数据!(无法保证写写操作原子性)
开发中的实践
- 推荐写法1:方法中不加事务
public void A(param){
update(record);
deleteKey(key);
}
- 推荐写法2:事务操作和更新缓存分开
public void A(param){
B(param);
deleteKey(key);
}
@Transactional(rollbackFor = Exception.class)
public void B(param){
update1(record1);
update2(record2);
}
- 错误写法:事务中更新db和更新缓存 直接嗝屁~ by:方法加上Transactional注解后,事务在方法执行完之后才进行提交!下面写法相当于先删除缓存,再更新db。
@Transactional(rollbackFor = Exception.class)
public void A(param){
update(record);
deleteKey(key);
}