如何保证缓存和数据库的一致性 (如何保证缓存一致性)

教程大全 2025-07-18 18:48:42 浏览

很多小伙伴在面试的时候,应该都遇到过类似的问题,如何确保缓存和数据库的一致性?

如果你对这个问题有过研究,应该可以发现这个问题其实很好回答,如果第一次听到或者第一次遇到这个问题,估计会有点懵,今天我们来聊聊这个话题。

1. 问题分析

首先我们来看看为什么会有这个问题!

我们在日常开发中,为了提高数据响应速度,可能会将一些热点数据保存在缓存中,这样就不用每次都去数据库中查询了,可以有效提高服务端的响应速度,那么目前我们最常使用的缓存就是 Redis 了。

用 Redis 做缓存,并不是一说缓存就是 Redis,还是要结合业务的具体情况,我们可以根据不同业务对数据要求的实时性不同,将数据分为三级,以电商项目为例:

选中合适的数据存入 Redis 之后,接下来,每当要读取数据的时候,就先去 Redis 中看看有没有,如果有就直接返回;如果没有,则去数据库中读取,并且将从数据库中读取到的数据缓存到 Redis 中,大致上就是这样一个流程,读取数据的这个流程实际上是比较清晰也比较简单的,没啥好说的。

然而,当数据存入缓存之后,如果需要更新的话,往往会来带另外的问题:

怎么办?正常来说,我们有四种方案:

到底使用哪种?

在回答这个问题之前,我们不妨先来看看三个经典的缓存模式:

2. Cache-Aside

Cache-Aside,中文也叫旁路缓存模式,如果我们能够在项目中采用 Cache-Aside,那么就能够尽可能的解决缓存与数据库数据不一致的问题,注意是尽可能的解决,并无法做到绝对解决。

Cache-Aside 又分为读缓存和写缓存两种情况,我们分别来看。

2.1 读缓存

先来看一张流程图:

它的流程是这样:

这是 Cache-Aside 的读缓存流程。

其实对于读缓存的流程而言,大家一般都没什么异议,有异议的主要是写流程,我们继续来看。

2.2 写缓存

先来看一张流程图:

这个写缓存的流程就比较简单,先更新数据库中的数据,然后删除旧的缓存即可。

流程虽然简单,但是却引伸出来两个问题:

我们来分别回答这两个问题。

为什么是删除旧缓存而不是更新旧缓存?

可以看到,有两个并发的线程 A 和 B:

那么此时,缓存中保存的数据就是不正确的,而如果采用了删除缓存的方式,就不会发生这种问题了。

为什么不先删除旧的缓存,然后再更新数据库?

这个也是考虑到并发请求,假设我们先删除旧的缓存,然后再更新数据库,那么就有可能出现如下这种情况:

这个操作是这样的,有两个线程,A 和 B,其中 A 写数据,B 读数据,具体流程如下:

一套操作下来,我们发现数据库和缓存中的数据不一致了!所以,在 Cache-Aside 中是先更新数据库,再删除缓存。

2.3 延迟双删

其实无论是先更新数据库再删除缓存,还是先删除缓存再更新数据库,在并发环境下都有可能存在问题:

假设有 A、B 两个并发请求:

当然我们前面已经分析过了,尽量先操作数据库再操作缓存,但是即使这样也还是有可能存在问题,解决问题的办法就是延迟双删。

延迟双删是这样:先执行缓存清除操作,再执行数据库更新操作,延迟 N 秒之后再执行一次缓存清除操作,这样就不用担心缓存中的数据和数据库中的数据不一致了。

那么这个延迟 N 秒,N 是多大比较合适呢?一般来说,N 要大于一次写操作的时间,如果延迟时间小于写入缓存的时间,会导致请求 A 已经延迟清除了缓存,但是此时请求 B 缓存还未写入,具体是多少,就要结合自己的业务来统计这个数值了。

2.4 如何确保原子性

但是更新数据库和删除缓存毕竟不是一个原子操作,要是数据库更新完毕后,删除缓存失败了咋办?

对于这种情况,一种常见的解决方案就是使用消息中间件来实现删除的重试。大家知道,MQ 一般都自带消费失败重试的机制,当我们要删除缓存的时候,就往 MQ 中扔一条消息,缓存服务读取该消息并尝试删除缓存,删除失败了就会自动重试。如果小伙伴们还不懂 RabbitMQ 的使用,可以在公众号江南一点雨后台回复 rabbitmq,有免费的视频+文档。

3. Read-Through/Write-Through

这种缓存操作模式,松哥印象最深的是在 Oracle Coherence 中有应用,不知道小伙伴们有没有用过 Oracle Coherence,这是一个内存数据网格,通过这个,应用开发人员和管理人员可快速访问键值数据,Coherence 可提供集群式低延迟数据存储、多语言网格计算和异步事件流处理,从而为客户企业应用赋予超高水平的可扩展性和性能。

Oracle Coherence 我们就不讨论了,我们就来说说 Read-Through。

3.1 Read-Through

这里为了省事,我就不自己画图了,网上找了一张图片,如下:

乍一看,很多人感觉这和 Cache-Aside 一样呀,没啥区别!是的,单看流程是不太容易看到区别。

Read-Through 是一种类似于 Cache-Aside 的缓存方法,区别在于,在 Cache-Aside 中,由应用程序决定去读取缓存还是读取数据库,这样就会导致应用程序中出现了很多业务无关的代码;而在 Read-Through 中,相当于多出来了一个中间层 Cache Middleware,由它去读取缓存或者数据库,应用层的代码得到了简化,松哥之前写过 Spring Cache 的用法,大家回忆下 Spring Cache 中的 @Cacheable 注解,感觉像不像 Read-Through?

我画一个简单的流程图大家来看下:

可以看到,和 Cache-Aside 相比,其实就相当于是多了一个 Cache Middleware,这样我们在应用程序中就只需要正常的读写数据就行了,并不用管底层的具体逻辑,相当于把缓存相关的代码从应用程序中剥离出来了,应用程序只需要专注于业务就行了。

3.2 Write-Through

Write-Through 其实也是差不多,所有的操作都交给 Cache Middleware 来完成,应用程序中就是一句简单的更新就行了,我们来看看流程:

在 Write-Through 策略中,所有的写操作都经过 Cache Middleware,每次写入时,Cache Middleware 会将数据存储在 DB 和 Cache 中,这两个操作发生在一个事务中,因此,只有两个都写入成功,一切才会成功。

这种写数据的优势在于,应用程序只与 Cache Middleware 对话,所以它的代码更加干净和简单。

4. Write Behind

Write-Behind 缓存策略类似于 Write-Through 缓存,应用程序仅与 Cache Middleware 通信,Cache Middleware 会预留一个与应用程序通信的接口。

Write-Behind 与 Write-Through 最大的区别在于,前者是数据首先写入缓存,一段时间后(或通过其他触发器)再将数据写入>

参考资料:


4、空间数据库中,矢量数据的管理方式有哪些,各有什么优缺点?

1、文件-关系数据库混合管理方式不足:①属性数据和图形数据通过ID联系起来,使查询运算,模型操作运算速度慢;② 数据分布和共享困难;③属性数据和图形数据分开存储,数据的安全性、一致性、完整性、并发控制以及数据损坏后的恢复方面缺少基本的功能;④缺乏表示空间对象及其关系的能力。 因此,目前空间数据管理正在逐步走出文件管理模式。 2、全关系数据库管理方式对于变长结构的空间几何数据,一般采用两种方法处理。 ⑴ 按照关系数据库组织数据的基本准则,对变长的几何数据进行关系范式分解,分解成定长记录的数据表进行存储。 然而,根据关系模型的分解与连接原则,在处理一个空间对象时,如面对象时,需要进行大量的连接操作,非常费时,并影响效率。 ⑵ 将图形数据的变长部分处理成Binary二进制Block块字段。 3、对象-关系数据库管理方式由于直接采用通用的关系数据库管理系统的效率不高,而非结构化的空间数据又十分重要,所以许多数据库管理系统的软件商在关系数据库管理系统中进行扩展,使之能直接存储和管理非结构化的空间数据。 这种扩展的空间对象管理模块主要解决了空间数据的变长记录的管理,由数据库软件商进行扩展,效率要比前面所述的二进制块的管理高得多。 但是它仍然没有解决对象的嵌套问题,空间数据结构也不能内用户任意定义,使用上仍受到一定限制。 矢量图形数据与属性数据的管理问题已基本得到解决。 从概念上说,空间数据还应包括数字高程模型、影像数据及其他专题数据。 虽然利用关系数据库管理系统中的大对象字段可以分块存贮影像和DEM数据,但是对于多尺度DEM数据,影像数据的空间索引、无缝拼接与漫游、多数据源集成等技术还没有一个完整的解决方案。

请大伙给我解释一下数据库设计的基本原则!

设计数据库不应该有这些: 1数据冗余 2不一致性 3插入异常 4删除异常

这图就出现了问题 如人工智能的学分不一致有两个文化学这就出现了以上的问题 所以要杜绝 我们可以这样分为两个表 如下:

右边的表只要把人工智能的删除一个就好了(画错了不好意思)

在就是函数的一些关系 如函数依赖 :

v函数依赖设R(U)是一个属性集U上的关系,X和Y是U的子集。如果属性集合X中每个属性的值构成的集合唯一地决定了属性集合Y中每个属性的值构成的集合,则属性集合Y函数依赖于属性集合X,计为:X→Y如下表所示,知道了“课程名”的值,即可知道“授课学时”的值。称“授课学时”函数依赖于“课程名”,或“课程名”可以决定“授课学时”,记作课程名→授课学时。

还有这个

v部分函数依赖:如果非主属性B函数依赖于构成某个候选关键字的一组主属性A的某一个真子集,则称B部分函数依赖于A。 v如“学分”函数依赖于主关键字{学号、课程}。 但决定“学分”的只是“课程”,与“学号”无关,则称“学分”部分函数依赖于{学号、课程} 。

传递关系:

v传递函数依赖的关系:在R (U)中,如存在X,Y,Z包含于U,且满足:X—>Y,Y—>Z,则称Z传递函数依赖于X。 v学生住宿的楼号依赖于学号,学生应交的住宿费是由楼号决定的,即“收费”依赖于“楼号”,“楼号”依赖于“学号”,则“收费”传递函数依赖于“学号”。

接下来的就是要符合范式:

第一范式:

任何符合关系定义的表即满足第一范式。

ID Name Sex Age Male Female 101 张三 Y 20 102 李四 Y 21

v第二范式™定义:如果一个关系不存在部分依赖关系,那么该关系就属于第二范式。 ™凡是以单个属性作为主关键字的关系自动就是第二范式。 因为主关键字只有一个,不会存在部分依赖的情况。 因此,第二范式只是针对主关键字是组合属性的关系。

第三范式v定义:一个关系如果是第二范式的,并且没有传递依赖关系,则该关系就是第三范式。 v每个非主属性不部分依赖于关键字,也不传递依赖于关键字的关系。

关系规范化的目的:解决关系模式中存在的插入、删除异常,以及数据冗余问题,基本思想:围绕函数依赖的主线,对一个关系模式进行分解,使关系从较低级范式变换到较高级范式。

以上也就是设计数据库基本注意的问题 我也是初学者 只能帮忙这些不知道是否对你有用!

如何保证缓存一致性

mysql可以使用oracle吗

区别如下:一、并发性并发性是oltp数据库最重要的特性,但并发涉及到资源的获取、共享与锁定。 mysql:mysql以表级锁为主,对资源锁定的粒度很大,如果一个session对一个表加锁时间过长,会让其他session无法更新此表中的数据。 虽然InNodB引擎的表可以用行级锁,但这个行级锁的机制依赖于表的索引,如果表没有索引,或者sql语句没有使用索引,那么仍然使用表级锁。 oracle:oracle使用行级锁,对资源锁定的粒度要小很多,只是锁定sql需要的资源,并且加锁是在数据库中的数据行上,不依赖与索引。 所以oracle对并发性的支持要好很多。 二、一致性oracle:oracle支持serializable的隔离级别,可以实现最高级别的读一致性。 每个session提交后其他session才能看到提交的更改。 oracle通过在undo表空间中构造多版本数据块来实现读一致性,每个session查询时,如果对应的数据块发生变化,oracle会在undo表空间中为这个session构造它查询时的旧的数据块。 mysql:mysql没有类似oracle的构造多版本数据块的机制,只支持read commited的隔离级别。 一个session读取数据时,其他session不能更改数据,但可以在表最后插入数据。 session更新数据时,要加上排它锁,其他session无法访问数据。 三、事务oracle很早就完全支持事务。 mysql在innodb存储引擎的行级锁的情况下才支持事务。 四、数据持久性oracle保证提交的数据均可恢复,因为oracle把提交的sql操作线写入了在线联机日志文件中,保持到了磁盘上,如果出现数据库或主机异常重启,重启后oracle可以考联机在线日志恢复客户提交的数据。 mysql:默认提交sql语句,但如果更新过程中出现db或主机重启的问题,也许会丢失数据。

本文版权声明本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,请联系本站客服,一经查实,本站将立刻删除。

发表评论

热门推荐