目录

一、概要

二、场景一:先更新数据库,再更新缓存

三、场景二:先更新缓存,再更新数据库

四、场景三:先删除缓存,再更新数据库

五、场景四:先更新数据库,再删除缓存

六、场景五:数据库主从同步导致数据不一致

七、总结


一、概要

缓存跟数据库不一致,指的是缓存中的数据跟数据库的数据出现了不一致,即其中一方存在脏数据的现象。需要注意的是,只有在对同一条数据并发读写的时候,才可能会出现这种问题。

  • 如果系统并发量很低,特别是读并发很低,那么它发生缓存跟数据库数据不一致的情况相对比较少,概率比较低;
  • 如果系统并发量很高,像淘宝、京东等电商平台,每天都是上亿级流量,每秒并发读是几万,每秒都有写请求,这种情况下出现缓存跟数据库不一致的概率就比较高;

下面我们详细分析常见的发生缓存与数据库不一致的场景。

二、场景一:先更新数据库,再更新缓存

假设有 2 个线程A 、B并发「写」id = 1的user数据,在高并发下可能会发生以下场景:

  1. 线程A更新数据库(name = 李四):update user set name = '李四' where id = 1;
  2. 线程B更新数据库(name = 王五):update user set name = '王五' where id = 1;
  3. 线程B更新缓存(name = 王五);
  4. 线程A更新缓存(name = 李四);

可以看到,线程B操作数据库和缓存的时间,却要比线程A的时间短,执行时序发生了「错乱」,此时线程B对缓存的更新就被覆盖掉了,最终导致id = 1的用户user的值在缓存中是"李四",在数据库中是"王五",缓存和数据库数据发生不一致。可见,先更新数据库,再更新缓存,当发生「写」并发时,也会存在数据不一致的情况。

大体过程如下图所示:

实际项目中通常不采用这种方式,主要基于如下一些原因:

  • 线程安全问题

如上分析的执行时序发生「错乱」,最终这条数据的结果是错误的,缓存跟数据库中其中一方的数据是脏数据。

  • 性能问题

如果采用先更新数据库,再更新缓存的方式,假如我们的系统写数据比较多,而读操作比较少,那么缓存将会被频繁地更新,这样导致缓存中的数据压根就没被读请求利用上,浪费性能。

三、场景二:先更新缓存,再更新数据库

这个比较简单,假设线程A需要写数据,如执行update user set name = '李四' where id = 1,此时线程A先更新缓存数据为"李四",然后更新数据库的时候,抛异常了,失败了,导致缓存更新成功,数据库更新失败,这就造成了两者的不一致,此时如果刚好有一个线程过来读数据:select * from user where id = 1,那么从缓存中读取到的数据就是脏数据。

实际项目中通常不采用更新缓存的方式,而采用删除缓存的方式。

  • 如果更新缓存成功,数据库更新失败,此时发生不一致,缓存中的是脏数据;
  • 如果采用删除缓存的方式,如果删除缓存成功,数据库更新失败,这时候查请求大不了去数据库查询到旧值,也总比从缓存中拿到脏数据要好一些;

四、场景三:先删除缓存,再更新数据库

假设有 2 个线程A 、B并发「读写」id = 1的user数据,可能会发生以下场景:

  1. 线程 A 要更新name = 李四(旧值 name = 张三):update user set name = '李四' where id = 1;
  2. 线程 A 先删除缓存;
  3. 线程 B 读缓存,发现不存在,从数据库中读取到旧值(name = 张三);
  4. 线程 A 将新值写入数据库(name = 李四):select * from user where id = 1;
  5. 线程 B 将旧值写入缓存(name = 张三);

最终id = 1的用户user的值在缓存中是"张三(旧值)",在数据库中是"李四(新值)",缓存和数据库数据发生不一致。可见,先删除缓存,后更新数据库,当发生「读+写」并发时,还是存在数据不一致的情况。

大体过程如下图所示:

前面已经介绍了先删除缓存,再更新数据库导致数据不一致的场景,那么怎么解决呢?答案是采用

【延时双删策略+缓存超时设置】结合起来。

  • 设置缓存过期时间

所有的写操作以数据库为准,只要到达缓存过期时间,则后面的读请求自然会从数据库中读取新值,然后再写入缓存中;

  • 延时双删策略

双删,其实就是删两次缓存的意思。延时,指的是在第一次删完缓存后,延迟一段时间,比如5秒或其他时间,然后再进行第二次删除缓存。

主要过程如下:

  1. 先删除缓存;
  2. 再写数据库;
  3. 休眠一段时间;
  4. 再次删除缓存;

至于需要休眠多少,这个延迟时间很难评估,读者需要根据自己项目读数据响应时间具体给一个大概的值,然后延迟时间一般在读数据响应时间基础上,再加上几百毫秒,或者一秒即可,这样就可以确保写请求可以删除读请求造成的缓存脏数据

参考建议:

1、第一次删除缓存可以采用同步方式删除,第二次删除缓存如果读者朋友担心同步删除会影响性能的话,可以采用异步线程去删;

2、很多时候,都是凭借经验大致估算这个延迟时间,例如笔者项目中通常延迟5s后第二次删除缓存,然后配置最大重试次数,确保缓存删除成功,当然只能尽可能地降低不一致的概率,极端情况下还是会发生不一致;

3、如果第二次删除缓存失败了怎么办?当然是不断地循环尝试删除缓存,可以将删除失败的记录发送到消息队列,然后可以不断重试删除,可以配置最大重试次数,配置告警,直到删除成功。

五、场景四:先更新数据库,再删除缓存

a、第一种情况:

线程A需要修改数据,update user set name = '李四' where id = 1,数据更新完成后,在删除缓存的时候,数据库宕机或者服务宕机,导致没有删除掉缓存,此时数据库和缓存的数据也会出现不一致,数据库中是新数据,缓存还是旧数据。

b、第二种情况:

假设有 2 个线程A 、B并发「读写」id = 1的user数据,可能会发生以下场景:

  1. 线程A查询时,缓存刚好过期了;
  2. 线程A从数据库中查询到旧值(name = 张三):select * from user where id = 1;
  3. 线程B更新数据库(name = 李四):update user set name = '李四' where id = 1;
  4. 线程B删除缓存,因为缓存已经过期,所以删除操作实际上什么都没做;
  5. 线程A将旧值(张三)写入缓存;

最终id = 1的用户user的值在缓存中是"张三(旧值)",在数据库中是"李四(新值)",缓存和数据库数据发生不一致。可见,先更新数据库,再删除缓存,当发生「读+写」并发时,同样也会存在数据不一致的情况。

大体过程如下图所示:

仔细分析一下,这种情况真的会发生么?准确地说,只是理论上会发生,概率很小,仔细想想,数据库的读操作的速度肯定是远快于写操作的,主要是以下原因:

  • 缓存刚好已失效读请求 + 写请求并发更新数据库 + 删除缓存的时间(步骤 3和4),要比读数据库 + 写缓存时间短(步骤 2 和 5);
  • 写数据库一般会先「加锁」,所以写数据库,通常是要比读数据库的时间更长;

经过前面的分析,我们发现这种方式发生数据不一致的概率相对较少,实际项目中可以采用这种方式,同样可以使用延迟双删策略,但为了保证两步都成功执行,最好再配合「消息队列」或「订阅变更日志」等重试方式,更加可靠。

六、场景五:数据库主从同步导致数据不一致

假设有 2 个线程A 、B并发「读写」id = 1的user数据,可能会发生以下场景:

  1. 线程A进行写操作,删除缓存;
  2. 线程A更新数据库:update user set name = '李四' where id = 1;
  3. 线程B查询缓存发现,缓存中没有值:select * from user where id = 1;
  4. 线程B去从库查询,这时,还没有完成主从同步,因此线程B在从库查询到的是其实是旧值(name = 张三);
  5. 线程B将旧值写入缓存(name = 张三);
  6. 数据库完成主从同步,从库变为新值(name = 李四);

可以看到,由于主从数据库同步的延时,也会导致缓存与数据库数据不一致。

如何解决呢?

同样可以采用延迟双删的方式,只是第二次删除缓存的休眠时间设置为【主从数据库同步的延迟时间 + 几百ms】,当然也可以通过Canal监听从库binlog日志的方式,将数据库变更信息发送到消息队列中,然后我们去监听消息队列,异步删除Redis对应的缓存。

七、总结

前面详细介绍了缓存与数据库发生不一致的场景,通常情况下,我们可以选择使用【先删除缓存,再更新数据库】或者【先更新数据库,再删除缓存】其中一种,如果是延迟双删的话,第二次删除缓存尽量采用异步线程池去删,并结合一些重试机制,再加上最大重试次数,尽可能避免缓存与数据库数据发生不一致。

如何解决缓存与数据库不一致?相关推荐

  1. 09丨缓存异常:如何解决缓存和数据库的数据不一致问题

    1.缓存和数据库的数据不一致是如何发生的?   首先,我们得清楚"数据的一致性"具体是啥意思.其实,这里的"一致性"包含了两种情况: 缓存中有数据,那么,缓存的 ...

  2. onbeforeedit和onbeginedit数据不一致_Redis缓存与数据库产生不一致的问题该如何解决?...

    不一致产生的原因 我们在使用redis过程中,通常会这样做:先读取缓存,如果缓存不存在,则读取数据库.伪代码如下: Object stuObj = new Object();public Stu ge ...

  3. Redis 缓存穿透、雪崩、缓存数据库不一致、持久化方式、分布式锁、过期策略

    1. Redis 缓存穿透 1.1 Redis 缓存穿透概念 访问了不存在的 key,缓存未命中,请求会穿透到 DB,量大时可能会对 DB 造成压力导致服务异常. 由于不恰当的业务功能实现,或者外部恶 ...

  4. Redis缓存穿透-热点缓存并发重建-缓存与数据库双写不一致-缓存雪崩

    解决缓存问题 1.解决Redis把内存爆满的三种方法 1.1 定期删除 1.2 惰性删除 1.3 内存淘汰策略 2. 缓存穿透--缓存击穿--缓存雪崩 3. 如何解决线上缓存穿透问题 3.1 缓存击穿 ...

  5. 教你从0到1搭建秒杀系统-缓存与数据库双写一致

    本文是秒杀系统的第四篇,我们来讨论秒杀系统中缓存热点数据的问题,进一步延伸到数据库和缓存的双写一致性问题. 在秒杀实际的业务中,一定有很多需要做缓存的场景,比如售卖的商品,包括名称,详情等.访问量很大 ...

  6. Redis缓存与数据库双写一致性

    前言: 首先,缓存由于其高并发和高性能的特性,已经在项目中被广泛使用.在读取缓存方面,大家没啥疑问,都是按照下图的流程来进行业务操作.         但是在更新缓存方面,对于更新完数据库,是更新缓存 ...

  7. 缓存和数据库一致性问题,看这篇就够了

    作者 | Magic Kaito 来源 | 水滴与银弹 如何保证缓存和数据库一致性,这是一个老生常谈的话题了. 但很多人对这个问题,依旧有很多疑惑: 到底是更新缓存还是删缓存? 到底选择先更新数据库, ...

  8. Redis 缓存实战——缓存、数据库一致性问题分析与解决方案

    引言 缓存与数据库一致性的问题自从出现了缓存概念后就一直被提及,它是一个缓存方案的先天缺陷,只要存在缓存,就势必会讨论缓存与数据库一致性的问题. 一致性问题还广泛存在于各种分布式存储场景中,如主从一致 ...

  9. 缓存和数据库如何保证一致性

    缓存和数据库如何保证一致性 1. 问题提出 正常情况下,我们从客户端发起请求从数据库拿到数据 当客户端请求变多的时候,我们就会引入缓存来提高接口的性能 但是,由于请求的并发性和数据处理的顺序性,更新数 ...

  10. 缓存和数据库一致性问题

    文章目录 1. 缓存和数据库一致性问题 2. 三个经典的缓存模式 2.1 Cache-Aside 2.1.1 读缓存 2.1.2 写缓存 2.1.3 延迟双删 2.1.4 如何确保操作缓存和操作数据库 ...

最新文章

  1. MySQL时间慢了八个小时
  2. 【建模必备】遗传算法的定义与生物学基础
  3. 博士发公众号文章也能评国奖?双一流高校新规引热议!
  4. 坐标轴 日期格式_Excel图表技巧之不连续的日期坐标轴怎么显示
  5. 单例设计模式-静态内部类
  6. 都2021年了,c/c++开发竟然还能继续吃香??
  7. Python编程实例(4)
  8. linux c语言头文件 在另外的地方,linux下的c语言的头文件在windows下头文件是哪几个?...
  9. html 发送ping帧,HTML5:ping属性之死亡ping与隐私追踪
  10. jqTransform表单美化
  11. URL和URI的区别和联系
  12. 多多客API SDK【拼多多开放平台】
  13. 微信小程序:计算器(附源码)
  14. 两个服务器同步网站数据,两个服务器之间数据库同步
  15. mysql etimedout_NodeJS:MySQL有时会引发ETIMEDOUT错误
  16. 厦门在职研究生计算机专业,2019年厦门大学在职研究生有什么专业可以选择
  17. macbook更新系统服务器,mac系统怎么更新_苹果笔记本系统如何更新-win7之家
  18. 【python逻辑算法题】一只青蛙一次可以跳上1级台阶,也可以跳上2级.求该青蛙跳上一个n级的台阶总共有多少种跳法
  19. 老毛桃u盘重装win7教程
  20. win10 ,jkd8 查看线程状态

热门文章

  1. 小程序支付一定要后台服务器,2.字节跳动小程序支付配置
  2. TensorFlow by Google CNN分类真实图片 Machine Learning Foundations: Ep #5 - Classifying real-world images
  3. LeetCode--Reverse Integer(整数反转)Python
  4. 华为天才少年-廖明辉
  5. Checkbox和RadioRadioButton及其实例
  6. 全国计算机应用能力三个模块,全国专业技术人员计算机应用能力考试科目模块Word...
  7. php 调用dll静态库,vue-cli 2.x 项目优化之引入本地静态库文件
  8. centos中mysql操作命令_CentOS系统常用的MySQL操作命令总结
  9. CS231n李飞飞计算机视觉 迁移学习之物体定位与检测上
  10. 现代通信原理A.3:随机信号的功率谱估计