数据库与缓存一致性问题
使用缓存目的
通常情况下,我们使用缓存的主要目的是为了提高查询性能,大多数情况下,我们是这样使用缓存的:
- 首先用户请求过来,会先查询缓存中有没有想要的数据,如果有就直接返回。
- 如果缓存中没有该数据,就去查询数据库。
- 如果数据库中有数据,就将查询出来的数据,放入缓存中,然后返回该数据。
- 如果数据库中也没有该数据,就返回空。
那如果有数据需要更新,应该怎么操作呢?
目前有4种方案:
- 先写缓存,再写数据库
- 先写数据库,再写缓存
- 先删缓存,再写数据库
- 先写数据库,再删缓存
先写缓存,再写数据库
如果采用先写缓存,再写数据库的方式,会有这种问题(是四种方式中最严重的):
用户执行写操作,首先写缓存,然后此时网络出现了问题,导致没有成功写到数据库
那这样的话,缓存更新为了最新数据,而数据库还是旧数据,这样缓存中的数据就变成了“脏数据”。我们都知道缓存的主要目的是把数据库中的数据临时保存在内存,便于后续查询,提升查询速度。但如果某条数据,在数据库中都不存在,你缓存这种“假数据
”又有啥意义呢?因此,先写缓存,再写数据库的方案是不可取的,在实际工作中用得不多。
先写数据库,再写缓存
用户的写操作,先写数据库,再写缓存,可以避免之前“假数据”的问题。但它却带来了新的问题。
写缓存失败了
如果把写数据库和写缓存操作,放在同一个事务当中,当写缓存失败了,我们可以把写入数据库的数据进行回滚。
如果是并发量比较小,对接口性能要求不太高的系统,可以这么玩。
但如果在高并发的业务场景中,写数据库和写缓存,都属于远程操作。为了防止出现大事务,造成的死锁问题,通常建议写数据库和写缓存不要放在同一个事务中。
也就是说在该方案中,如果写数据库成功了,但写缓存失败了,数据库中已写入的数据不会回滚。
这就会出现:数据库是新数据
,而缓存是旧数据
,两边数据不一致
的情况
高并发下的问题
假设在高并发的场景中,针对同一个用户的同一条数据,有两个写数据请求:a和b,它们同时请求到业务系统。
其中请求a获取的是旧数据,而请求b获取的是新数据,如下图所示:
- 请求a先过来,写完了数据库,由于网络问题,还没来得及写缓存。
- 这时请求b过来了,先写了数据库。
- 接下来,请求b顺利写了缓存。
- 此时,请求a的卡顿结束,也写了缓存。
很显然,在这个过程中,请求b在缓存中的新数据,被请求a中的旧数据覆盖了。也就是说,在高并发场景下,如果多个线程同时执行先写数据库,再写缓存的操作,可能出现数据库中是新值,而缓存中是旧值的情况。
浪费系统资源
如果写的缓存,并不是简单的数据内容,而是要经过非常复杂的计算得出的最终结果。这样每写一次缓存,都要经过一次非常复杂的计算,就会十分浪费资源(CPU和内存)。
对一些写操作多、读操作少的业务场景,这种做法也是得不偿失。
先删缓存,再删数据库
高并发下的问题
假设在高并发的场景中,同一个用户的同一条数据,有一个读数据请求c,还有另一个写数据请求d(一个更新操作),同时请求到业务系统。如下图所示:
请求d先过来,把缓存删除了,由于网络原因,卡顿了一下,还没来得及写数据库。
这时请求c过来了,查缓存发现没有数据,再查数据库,有数据,但是旧值。
请求c将数据库中的旧值更新到缓存中。
此时,请求d卡顿结束,把新值写入数据库。
在这个过程当中,