数据库和缓存不一致问题
缓存不一致场景
先写数据库后更新缓存
| Thread 1 | Thread 2 |
|---|---|
| 写数据库,值为 10 | |
| 写数据库,值为 20 | |
| 写缓存,值为 20 | |
| 写缓存,值为 10 |
最终,数据库中的值为 20,缓存中为 10
先更新缓存后写数据库
| Thread 1 | Thread 2 |
|---|---|
| 写缓存,值为 10 | |
| 写缓存,值为 20 | |
| 写数据库,值为 20 | |
| 写数据库,值为 10 |
最终,数据库中的值为 10,缓存中为 20
先读缓存后更新数据库
| Thread 1 | Thread 2 |
|---|---|
| 读缓存,值为空 | |
| 读数据库,值为 10 | |
| 更新缓存,值为 10 | 写数据库,值为 20 |
最终,数据库中的值为 20,缓存中为 10
缓存不一致解决方案
常见的有三种方案:
- 先更新数据库,再删除缓存
- 延迟双删:先删除缓存,再更新数据库,再删除缓存
- cache-aside:更新数据库,基于 binlog 监听更新缓存
为什么删除缓存而不是更新缓存
上述三个方案中,1、2 方案都选择删除缓存而不更新,理由如下:
- 更新缓存会涉及序列化,无论需要更新的数据量的大小,开销都比删除缓存来得大
- 在 先写数据库后更新缓存 和 先更新缓存后写数据库 场景中,更新缓存会带来不一致的问题,而直接删除缓存则不会
当然,删除缓存会存在 cache miss 的问题,这个问题严重点可能导致 缓存击穿。
先写数据库还是先删除缓存
在确定删除缓存而不是更新缓存的前提下,我们紧接着需要思考的问题是,删除缓存和写数据库之间的顺序。
先删除缓存
先删除缓存再更新数据库有一个好处:如果更新数据库失败了,不会影响已有业务。因为即使缓存已经被删除了,当请求再次查询的时候,还是会把数据库中的数据更新到缓存中。
这种策略会在读写并发的时候遇到问题,考虑如下流程:
| Thread 1 | Thread 2 |
|---|---|
| 删除缓存,值为 10 | |
| 读取缓存,值为空 | |
| 读取数据库,值为 10 | |
| 修改数据库,值为 20 | |
| 更新缓存,值为 10 |
会导致缓存和数据库不一致。
先写数据库
此策略的好处是,先写数据库能确保持久层数据的准确,能保证数据最终的可靠性和一致性。
此策略的问题是,如果缓存删除失败了,也会导致数据不一致的情况。那怎么解决呢?可以使用 延迟双删。
如何选择
如果业务量不大,并发不高,可以选择 先写数据库,因为这种方案更简单。反之选择先删除缓存再更新数据库,通过引入延迟双删、分布式锁等机制,减少并发带来的问题。当然,这种方式的复杂的也更高。