天道不一定酬所有勤
但是,天道只酬勤
Hollis出品的全套Java面试宝典不来了解一下吗?

新入职的同事问我,为什么会出现数据库和缓存不一致的问题?

Hollis出品的全套Java面试宝典不来了解一下吗?

关于缓存,相信很多人都不陌生,我们通常会在数据库之上搭建一个缓存服务器,将一些高频的数据存储到缓存中,可以提升查询效率,从而提高响应速度以及并发度。

但是,与此同时也带来了一个问题,那就是如何保证缓存和数据库之间的数据一致性?

在讨论怎么做之前,我们先来看看为什么会出现缓存一致性的问题呢?这些问题是如何发生的呢?

非并发的情况

首先,我们在非并发的场景中,出现不一致的问题大家都能比较容易的理解,因为缓存的操作和数据库的操作是存在一定的时间差的

而且这两个操作是没办法保证原子性的,也就是说,是有可能一个操作成功,一个操作失败的。

所以,这就必然会存在不一致的情况。

但同时,因为我们的业务系统是开放给用户使用的,所以经常会出现各种各样的并发的场景,因为并发的存在,会使得数据一致性的问题更加的多。

并发的情况

对于数据的操作,无外乎就是读和写两种,那么就会同时存在"读读并发"、"读写并发"和"写写并发"。

对于两个读线程,即使发生并发,因为只是读的动作,所以不会有数据的变更,发生了并发的话也不会有数据不一致的情况。

接下来我们先来分析比较容易理解的"写写并发"的情况。

写写并发

因为在数据库和缓存的操作过程中,可能存在"先写数据库,后删缓存"、"先写数据库,后更新缓存"、"先删缓存库,后写数据库"以及"先更新缓存库,后写数据库"这四种。

其中"删缓存"的这两种是把缓存清空,所以"写写并发"不会存在缓存和数据库不一致的情况。我们就看"更新缓存"的这两种:

先写数据库,后更新缓存:

W W
写数据库,更新成20
写数据库,更新成10
写缓存,更新成10
写缓存,更新成20(数据不一致)

先更新缓存,后写数据库:

W W
写缓存,更新成20
写缓存,更新成10
写数据库,更新成10
写数据库,更新成20(数据不一致)

以上两种情况,都是两个写线程并发之后,因为乱序的问题,导致最终缓存中的值是20,而数据库中的值是10,最终导致缓存和数据库中的值不一致的情况。

除了写写并发之外,还有一种比较容易被忽视的情况,那就是读写之间的并发也会导致数据库和缓存的不一致。

读写并发

我们知道,当我们使用了缓存之后,一个读的线程在查询数据的过程是这样的:

1、查询缓存,如果缓存中有值,则直接返回 2、查询数据库 3、把数据库的查询结果更新到缓存中

所以,对于一个读线程来说,虽然不会写数据库,但是是会更新缓存的,所以,在一些特殊的并发场景中,就会导致数据不一致的情况。

读写并发的时序如下:

W R
读缓存,缓存中没有值
读数据库,数据库中得到结果为10
写数据库和缓存,更新成20
写缓存,更新成10(数据不一致)

也就是说,假如一个读线程,在读缓存的时候没查到值,他就会去数据库中查询,但是如果自查询到结果之后,更新缓存之前,数据库被更新了,但是这个读线程是完全不知道的,那么就导致最终缓存会被重新用一个"旧值"覆盖掉。

这也就导致了缓存和数据库的不一致的现象

但是这种现象其实发生的概率比较低,因为一般一个读操作是很快的,数据库+缓存的读操作基本在十几毫秒左右就可以完成了。

而在这期间,更好另一个线程执行了一个比较耗时的写操作的概率确实比较低。

当然,根据墨菲定律,只要有可能发生的事情,就一定会发生。所以我们也要引起重视。

总结

我们在本文中介绍了数据库和缓存因为双写存在的不一致的情况,无论是单线程还是多线程,都是有可能会出现数据不一致的。

本文中还提到了在数据库和缓存的操作过程中,可能存在"先写数据库,后删缓存"、"先写数据库,后更新缓存"、"先删缓存库,后写数据库"以及"先更新缓存库,后写数据库"这四种。

那么,到底是应该删除缓存好呢,还是更新缓存好呢?到底应该先操作数据库呢还是先操作缓存呢?哪种方案更好呢?又该如何选择呢?

我们在后面的文章中再展开介绍。后面几篇文章会基于本文作为前提展开,所以大家要先能理解不一致问题出现在什么时候,才能知道要怎么做才能解决。

赞(1)
如未加特殊说明,此网站文章均为原创,转载必须注明出处。HollisChuang's Blog » 新入职的同事问我,为什么会出现数据库和缓存不一致的问题?
Hollis出品的全套Java面试宝典不来了解一下吗?

评论 抢沙发

HollisChuang's Blog

联系我关于我