参考文档:
缓存和数据库到底先写哪个?
1. 引出问题
在数据库和缓存一致性问题上,有以下几个操作
- 先删缓存,再更新数据库
- 先更新数据库,再更新缓存
- 先更新数据库,再删缓存
第一种方式
这个是逻辑是错误的。试想,两个并发操作,一个是更新操作,另一个是查询操作,更新操作删除缓存后,查询操作没有命中缓存,先把老数据读出来后放到缓存中,然后更新操作更新了数据库。于是,在缓存中的数据还是老的数据,导致缓存中的数据是脏的,而且还一直这样脏下去了。
2. Cache Aside Pattern
这是我们在维持缓存一致性问题上最常用的方案,就是上述第三种方案,先更新数据库,再删缓存(使缓存失效)。
一个是查询操作,一个是更新操作的并发,首先,没有了删除 cache 数据的操作了,而是先更新了数据库中的数据,此时,缓存依然有效,所以,并发的查询操作拿的是没有更新的数据,但是,更新操作马上让缓存的失效了,后续的查询操作再把数据从数据库中拉出来,缓存中就变成了新数据。
那么这种设计模式会不会出问题?
比如,一个是读操作,但是没有命中缓存,然后就到数据库中取数据,此时来了一个写操作,写完数据库后,让缓存失效,然后,之前的那个读操作再把老的数据放进去,所以,会造成脏数据。
但,这个case理论上会出现,不过,实际上出现的概率可能非常低,因为这个条件需要发生在读缓存时缓存失效,而且并发着有一个写操作。而实际上数据库的写操作会比读操作慢得多,而且还要锁表,而读操作必需在写操作前进入数据库操作,而又要晚于写操作更新缓存,所有的这些条件都具备的概率基本并不大。
我们为什么不采用第二种方案
防止两个并发的写操作导致脏数据。
3. 优化
其实,谁好谁坏,选择哪一个,并没有那么绝对,两种策略在并发情况下,都存在不一致的可能。目前工程应用上,对先删除缓存,再更新数据库有两个主要优化:
- 延时双删(缓存删了又怕被污染,延时再删一次)
- 异步更新缓存(基于订阅binlog的同步机制,阿里的canal、腾讯的Mysync,并发缓存更新串行化)
通过这些措施,先删除缓存,再更新数据库 ,也能满足业务的一致性要求,所以,没有完美的解决方案,只有适合业务场景。