当前位置: 首页 > news >正文

缓存与数据库数据一致性问题

在用了redis缓存的系统中,正常情况下,一个读操作会先查缓存,如果在缓存中查到了,则直接返回,如果缓存中没有,则会查数据库,再将查到的数据写到redis中,然后返回。如下图:

 

 这样的流程没有啥问题,但是当数据变更时,怎么把缓存变到最新,这就是缓存一致性要考虑的问题。

首先最容易想到的就是在更新数据库的时候同时更新缓存。这就涉及到先更新缓存还是先更新数据库的问题。其实无论是先操作哪一个都可能会出现缓存不一致问题。

首先就是两个操作不是原子性的。就可能出现一个成功一个失败的情况。先更新数据库,然后缓存更新失败,这样就会导致缓存中存在的是一个错误的旧值。先更新缓存,数据库更新失败时,这样读取到的缓存依然是不正确的。这还是没有并发的情况。

其次考虑在并发场景下,两个线程同时进行先更新缓存后更新数据库的操作:

 先更新数据库后更新缓存会导致一样的问题:

 然后还有一个发生概率比较低的并发场景,那就是读写并发。读的过程就是一开始说的先查缓存,缓存中有直接返回,没有则查数据库,查完再写到缓存然后返回。如果读写次序是:读线程先读缓存,缓存没有,查数据库值假如为10,这时候更新缓存和数据库的写操作执行了写数据库和缓存为20,然后读线程接着又更新缓存它从数据库查到的10。数据库中为20,缓存中为10,又导致了不一致,如图:

 所以更新缓存就不太行。就考虑直接删除缓存和更新数据库,由于是直接删除缓存,所以上面写写并发情况出现的不一致问题也就不存在了。而且删缓存相比于更新缓存更简单,也更不容易出错。因为很多情况下缓存中的数据可能并不是一个简单的字符串,而是一个比较大的JSON串,更新缓存要从缓存中反序列化得到对象然后更新,然后又要序列化成JSON再写到redis。而删缓存直接了当删掉,就很简单不容易出错。

但是删缓存依然存在两个操作无法保证原子性的问题:

假如先更新数据库后删缓存,删缓存失败了,就会导致缓存是旧值,数据不一致。

如果先删除缓存后更新数据库,即使更新数据库失败了也不会有脏数据,没什么影响,只要重试一次就好了。但是这种方案会无形中放大前面说的读写并发时的问题,因为先删缓存会增大缓存miss的概率(这种缓存Miss还可能会导致缓存击穿,可以通过加锁来解决)。还是前面的例子,一个读线程从缓存中没有查到值,然后查数据库查到10,这时候恰好一个写线程删缓存更新数据库为20,然后读线程更新缓存为10。这就又导致了不一致。

这种情况就要延迟双删来解决了,写线程先删缓存,写数据库后,延迟一会再删一次,这样就能把最后读线程写到缓存的脏数据给删掉了。延迟一般1~2秒。

总结一下延迟双删,第一次删除的目的是解决两个操作无法保证原子而导致不一致问题,所以选择先删缓存再更新数据库。第二次删除的目的是为了降低第一次删除而导致放大读写并发导致数据不一致的概率。

第二次删除失败怎么办?

也会出现,但第二次删除本来就是补偿性的。在读写并发这个小概率不一致事件中再失败,概率就更低。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • Git错误分析
  • Git 详解(原理、使用)
  • el-table 树状表格查询符合条件的数据
  • 基于Python进行分类算法实验(人工智能)的设计与实现
  • Selenium 的基本操作你知道哪些?
  • 短视频矩阵系统源代码开发---多种剪辑逻辑再次升级
  • 技术赋能政务服务:VR导视与AI客服在政务大厅的创新应用
  • [终端安全]-5 移动终端之操作系统安全
  • 数据库第三次作业
  • Java中toString()方法的理解使用及如何通过IDEA快速自动调用,重写toString()方法
  • Leetcode—97. 交错字符串【中等】
  • Doris数仓的最佳拍档ETLCloud数据集成平台
  • Git 快速上手
  • 下半年交火点:智驾全国都能开,智舱多模态大模型
  • gen_circle_contour_xld 创建XLD轮廓对应于圆或圆弧。
  • [分享]iOS开发-关于在xcode中引用文件夹右边出现问号的解决办法
  • 「前端早读君006」移动开发必备:那些玩转H5的小技巧
  • 10个最佳ES6特性 ES7与ES8的特性
  • IDEA 插件开发入门教程
  • Java 9 被无情抛弃,Java 8 直接升级到 Java 10!!
  • JavaScript实现分页效果
  • JAVA多线程机制解析-volatilesynchronized
  • js算法-归并排序(merge_sort)
  • MYSQL 的 IF 函数
  • Nginx 通过 Lua + Redis 实现动态封禁 IP
  • SQLServer之创建数据库快照
  • Vue2.x学习三:事件处理生命周期钩子
  • 跨域
  • 面试题:给你个id,去拿到name,多叉树遍历
  • 区块链将重新定义世界
  • 我与Jetbrains的这些年
  • 小程序button引导用户授权
  • Spring第一个helloWorld
  • ​卜东波研究员:高观点下的少儿计算思维
  • ​如何使用ArcGIS Pro制作渐变河流效果
  • ​水经微图Web1.5.0版即将上线
  • ‌JavaScript 数据类型转换
  • # 透过事物看本质的能力怎么培养?
  • #Datawhale X 李宏毅苹果书 AI夏令营#3.13.2局部极小值与鞍点批量和动量
  • ${factoryList }后面有空格不影响
  • (12)目标检测_SSD基于pytorch搭建代码
  • (42)STM32——LCD显示屏实验笔记
  • (AngularJS)Angular 控制器之间通信初探
  • (Java岗)秋招打卡!一本学历拿下美团、阿里、快手、米哈游offer
  • (Oracle)SQL优化技巧(一):分页查询
  • (pojstep1.1.2)2654(直叙式模拟)
  • (多级缓存)缓存同步
  • (附源码)计算机毕业设计SSM智慧停车系统
  • (六)库存超卖案例实战——使用mysql分布式锁解决“超卖”问题
  • (一)80c52学习之旅-起始篇
  • (转)Sql Server 保留几位小数的两种做法
  • .“空心村”成因分析及解决对策122344
  • .gitignore文件---让git自动忽略指定文件
  • .NET 5种线程安全集合
  • .Net组件程序设计之线程、并发管理(一)