漫谈数据库与缓存一致性

2021-11-08  本文已影响0人  lfboo

为了追求更高的响应速度,通常我们会引入缓存,而拒绝每次都请求DB。
引入缓存后,在更新数据时,不仅要更新DB数据,而且要更新缓存数据,这两个更新操作存在顺序问题:

  1. 先更新DB,再更新缓存
  2. 先更新缓存,再更新DB

两者都存在并发问题,分析如下:

先更新DB,再更新缓存

假设有两个请求,同时更新一条数据,则可能出现如下情况


image.png

request1 先更新DB为1,在更新缓存之前,request2更新DB为2,接着request2更新缓存为2,然后request1更新缓存为1
于是,结果就出现DB为2,缓存为1, 两者不一致。

先更新缓存,再更新DB

该情况同时存在并发问题,如:


image.png

request1先更新缓存为1,在更新DB之前,request2更新缓存为2, 接着request2更新DB为2,然后request1更新DB为1
于是,结果就出现缓存为2,DB为1,两者不一致

所以,无论是「先更新数据库,再更新缓存」,还是「先更新缓存,再更新数据库」,这两个方案都存在并发问题,当两个请求并发更新同一条数据的时候,可能会出现缓存和数据库中的数据不一致的现象。

于是很容易想到,更新数据时,不更新缓存,而是删除缓存,此时如果读数据发现缓存没数据,会再从DB中读数据,然后更新到缓存。
可是在更新数据时, 依然会存在删缓存和更新数据的顺序问题

先删缓存,再更新DB

假设某个用户的年龄是 20,request1要更新用户年龄为 21,所以它会删除缓存中的内容。这时,另一个request2要读取这个用户的年龄,它查询缓存发现未命中后,会从数据库中读取到年龄为 20,并且写入到缓存中,然后request1继续更改数据库,将用户的年龄更新为 21


image.png

最终,该用户年龄在缓存中是 20(旧值),在数据库中是 21(新值),缓存和数据库的数据不一致。
可以看到,先删除缓存,再更新数据库,在「读 + 写」并发的时候,还是会出现缓存和数据库的数据不一致的问题。

先更新DB,再更新缓存

继续用「读 + 写」请求的并发的场景来分析。

假如某个用户数据在缓存中不存在,request1读取数据时从数据库中查询到年龄为 20,在未写入缓存中时另一个request2更新数据。它更新数据库中的年龄为 21,并且清空缓存。这时request1把从数据库中读到的年龄为 20 的数据写入到缓存中。


image.png

最终,该用户年龄在缓存中是 20(旧值),在数据库中是 21(新值),缓存和数据库数据不一致。
从上面的理论上分析,先更新数据库,再删除缓存也是会出现数据不一致性的问题,但是在实际中,这个问题出现的概率并不高。

因为缓存的写入通常要远远快于数据库的写入,所以在实际中很难出现request2 已经更新了数据库并且删除了缓存,request1才更新完缓存的情况。

而一旦request1早于request2 删除缓存之前更新了缓存,那么接下来的请求就会因为缓存不命中而从数据库中重新读取数据,所以不会出现这种不一致的情况。

所以,「先更新数据库 + 再删除缓存」的方案,是可以保证数据一致性的。

理论上先更新数据库,再删除缓存是正常的,但理论上建立在更新数据库和删除缓存都成功的基础上的,如果更新数据库后,再删除缓存时失败了,则缓存依旧是旧值,依旧会存在DB和缓存数据不一致。

如果解决更新DB成功,删除缓存失败?

继续探讨

先更新数据库,再删除缓存的方案虽然保证了数据库与缓存的数据一致性,但是每次更新数据的时候,缓存的数据都会被删除,这样会对缓存的命中率带来影响。
所以,如果我们的业务对缓存命中率有很高的要求,我们可以采用「更新数据库 + 更新缓存」的方案,因为更新缓存并不会出现缓存未命中的情况。

但是这个方案前面我们也分析过,在两个更新请求并发执行的时候,会出现数据不一致的问题,因为更新数据库和更新缓存这两个操作是独立的,而我们又没有对操作做任何并发控制,那么当两个线程并发更新它们的话,就会因为写入顺序的不同造成数据的不一致。

所以我们得增加一些手段来解决这个问题,这里提供两种做法:

上一篇下一篇

猜你喜欢

热点阅读