Redis 缓存与数据库一致性:先删缓存还是先更新库的 4 种方案
个人主页 我不会起名字322 欢迎各位大佬莅临其他栏目 技术栈学习笔记 其他栏目 力扣Hot100题目解析 其他栏目 Go项目学习笔记 其他栏目 redis 其他栏目 mysql 文章目录一、先把读写流程定死二、四种写顺序逐个推演方案 1先更新数据库再更新缓存方案 2先更新数据库再删除缓存Cache Aside推荐方案 3先删除缓存再更新数据库方案 4先更新缓存再更新数据库四种方案横向对比三、为什么先更库再删缓存仍然会不一致场景 1读请求在删缓存之前拿到旧值在之后回填场景 2删缓存失败场景 3读写分离 复制延迟四、延迟双删能降低概率但不解决问题五、让不一致能自愈的三层兜底第 1 层删除失败必须重试第 2 层用 binlog 订阅替代业务里删缓存第 3 层TTL 是最终的一致性保证六、多级缓存别忘了本地缓存七、怎么选按业务容忍度决策小结先删缓存还是先更新数据库这个问题在面试里被问烂了但真正落到线上踩的往往不是选错顺序而是以为选对了顺序就不会不一致。先把结论摆出来缓存和数据库是两个独立的存储中间没有事务。除非你放弃缓存、或者上分布式事务否则一致性只能把窗口缩到业务能接受的程度做不到消除。所以下面所有的讨论本质都是在回答一句话脏数据的窗口有多大、会持续多久、失败之后能不能自愈。一、先把读写流程定死后面四种方案都基于同一套流程先约定清楚避免歧义读流程Cache Aside 1. GET cache:key → 命中直接返回 2. 未命中 → 查数据库 3. 把结果 SET 回缓存设 TTL 4. 返回 写流程四种方案的差别就在这两步的顺序 A. 操作数据库 B. 操作缓存更新 or 删除关键点读流程里有回填这一步。所有不一致的根源几乎都能归结为——写请求在动数据库/缓存的同时读请求把旧值回填了进去。二、四种写顺序逐个推演方案 1先更新数据库再更新缓存两个并发写请求时序交错时刻请求 A要写入值 2请求 B要写入值 3库里缓存里T1UPDATE 库 22旧值 1T2UPDATE 库 33旧值 1T3SET 缓存 333T4SET 缓存 232脏后果是长期的缓存里是 2库里是 3而缓存不会自己变回 3除非等 TTL 过期。这种写覆盖乱序是更新缓存方案最致命的地方。另外还有两个附加问题如果缓存的 value 是多次计算拼出来的每次写都重算一遍很浪费写多读少时纯亏缓存不是可靠存储可能被淘汰/宕机写进去了也可能没了。方案 2先更新数据库再删除缓存Cache Aside推荐还是并发写但因为删除是幂等的乱序不会造成长期脏数据时刻请求 A写入 2请求 B写入 3库里缓存里T1UPDATE 库 22旧值 1T2UPDATE 库 33旧值 1T3DEL 缓存3空T4DEL 缓存3空无论谁先谁后最后缓存都是空的下一次读会回填 3。这就是它成为主流方案的原因删除是幂等的乱序不产生长期脏数据。但它仍然有一个读请求插队的窗口见第四节。方案 3先删除缓存再更新数据库这个方案的窗口比想象中大得多时刻写请求读请求库里缓存里T1DEL 缓存1旧空T2查库 → 1 →回填 111旧的T3UPDATE 库 221脏注意 T2 和 T3 之间的间隔是整个写库耗时包含事务、索引更新、甚至这条 SQL 的排队时间。也就是说读请求在这段时间里插进来的概率远高于方案 2 里那个极短的窗口。挂掉的后果同样是长期脏数据。所以顺序推荐是明确的先更新库再删缓存。方案 4先更新缓存再更新数据库基本不能用缓存写成功、数据库失败时缓存里是新值、库里是旧值而且缓存无法回滚反过来若先成功数据库也救不了缓存侧。除非有补偿机制否则别选。四种方案横向对比方案脏数据窗口会不会长期不一致实现成本推荐度先更库 → 更缓存并发写错序会直到 TTL 过期低❌先更库 → 删缓存读请求插队窗口内短暂不一致低✅ 首选先删缓存 → 更库整个写库耗时很可能低❌先更缓存 → 更库写失败即不一致会高需补偿❌三、为什么先更库再删缓存仍然会不一致场景 1读请求在删缓存之前拿到旧值在之后回填时刻线程 A写线程 B读T1GET 缓存未命中T2查库读到旧值 1T3UPDATE 库 2T4DEL 缓存T5SET 缓存 1旧的结果缓存里是很旧的 1。它发生的条件是读请求查库这一步比写请求的更新库 删缓存更慢。现实中这个概率不高——写库通常要拿锁、写 undo、刷 binlog比一次主键查询慢得多——但概率低不等于不会尤其是大事务、慢 SQL、从库读取的场景。场景 2删缓存失败DEL超时、Redis 抖动、主从切换都会让删除失败。而失败之后没有任何机制会再来删一次脏数据就一直在那儿直到 TTL 到期。这是线上最常见的一种偶发不一致也是最容易被忽略的——很多人只写了redisTemplate.delete(key)连返回值都没看。场景 3读写分离 复制延迟先更新主库 → 删缓存 → 读请求落到从库从库还没同步完读到旧值并回填。此时缓存里是旧值且会持续到 TTL。如果你有读写分离这就是必须一起考虑的因素。四、延迟双删能降低概率但不解决问题标准写法是删一次、改库、睡一会儿、再删一次publicvoidupdateOrder(Orderorder){Stringkeyorder:order.getId();redis.delete(key);// 第一次清掉旧值orderMapper.updateById(order);// 更新数据库executor.schedule(()-{// 第二次延迟再删一次redis.delete(key);},800,TimeUnit.MILLISECONDS);}它解决的是场景 1把读请求在窗口内回填的旧值再清一次。但要用对必须回答三个问题延迟多久理论要求大于一次读请求查库 回填的耗时。经验值是 500ms1s但它没有任何保证——慢查询、GC、网络抖动都可能超过这个值。能不能 sleep不要在业务线程里Thread.sleep会占着 Tomcat 线程和数据库连接。用延迟队列、ScheduledExecutorService或 MQ 延时消息。第二次删除失败怎么办如果不重试等于没做。所以延迟双删是概率优化不是一致性方案。真正让系统自愈的是下面三件事。五、让不一致能自愈的三层兜底第 1 层删除失败必须重试把要删的 key落一条本地消息表或直接投递 MQ删除成功再标记完成失败就重投。要点是删除操作要幂等且可重放这也是删除比更新缓存更好用的原因。publicvoidevictWithRetry(Stringkey){for(inti0;i3;i){// 立即重试几次try{redis.delete(key);return;}catch(Exceptione){sleep(50Li);// 50/100/200ms 退避}}mq.send(cache-evict,key);// 还失败就交给 MQ 重投}第 2 层用 binlog 订阅替代业务里删缓存这是我认为最值得投入的一层把删缓存的时机从业务代码挪到数据库变更事件上。业务写入 → MySQL 提交 → binlog → Canal/Debezium 解析 → 投递到 MQ → 消费者按顺序删除对应缓存 key好处很实在删除动作与业务解耦业务只负责写库不用记得删缓存、binlog 是有序且完整的不会因为代码分支漏删、失败可以重放MQ 有重试和死信、还能顺手处理多级缓存和搜索索引的同步。代价是需要维护一个中间件、要处理缓存删除的延迟binlog 解析到消费有几十到几百毫秒所以仍然建议配合 TTL。第 3 层TTL 是最终的一致性保证无论前面几层多完善一定要给缓存设置过期时间。TTL 是最坏情况下多久能自愈的硬承诺强一致要求如账户余额TTL 设短几秒甚至考虑不走缓存一般商品/详情类TTL 几分钟到几十分钟配合上面的兜底足够静态字典类TTL 可以很长甚至主动预热。没有 TTL 的缓存 一次删除失败 永久脏数据。这是我见过最贵的一个省事。六、多级缓存别忘了本地缓存加了 Caffeine 之后DEL Redis对本地缓存无效本地缓存可能长时间提供旧值。可行做法广播失效通过 Redis Pub/Sub 或 MQ 广播key 失效消息各实例清本地缓存注意消息丢失时靠 TTL 兜底极短 TTL本地缓存只放 15 秒把不一致窗口控制在秒级版本号本地缓存里存一个全局版本号写操作递增版本读时校验版本是否落后。七、怎么选按业务容忍度决策业务对不一致的容忍度建议方案完全不能容忍金额、库存扣减不用缓存或只做只读缓存必须走数据库 分布式锁/乐观锁秒级可容忍订单详情、用户资料先更库再删缓存 删除重试 短 TTL几十秒分钟级可容忍商品列表、文章内容Cache Aside TTL 几分钟 binlog 订阅兜底小时级可容忍配置、字典TTL 长一些定期主动刷新即可小结顺序选先更新数据库、再删除缓存删除幂等乱序不产生长期脏数据先删缓存再更库的窗口是整整一个写库耗时明显更差。单靠顺序解决不了一致性必须配三样东西删除失败重试、binlog 订阅兜底、TTL。延迟双删只是降概率延迟时间没有理论保证别把它当成一致性方案。记住那句判断标准缓存一致性的问题不是会不会不一致而是不一致能持续多久、能不能自愈。

相关新闻

CSDN 博客模板:【保姆级】PyCharm 下载 + 安装 + 激活 + 首次配置教程(小白友好)

CSDN 博客模板:【保姆级】PyCharm 下载 + 安装 + 激活 + 首次配置教程(小白友好)

前言 简介摘要:本篇文章手把手教你从官网下载 PyCharm,Windows/macOS 系统安装,激活方式,首次启动配置、新建项目,附带大量避坑点,零基础也能一次成功搭建 Python 开发环境。 很多刚学 Python 的小伙伴&…

2026/10/9 10:25:49 阅读更多 →
【排样】亲士套料工具箱

【排样】亲士套料工具箱

本文涉及知识点 数学 几何 前言 运行环境:AutoCad2013及更高版本,暂不支持中文Cad和BricsCad。 帮助视频:https://edu.csdn.net/course/detail/41448 如果在审核中,请点击:https://www.bilibili.com/video/BV1X4Hm6…

2026/10/9 10:24:48 阅读更多 →
2026最新AI Agent零基础学习路线,小白也能轻松掌握并收藏!

2026最新AI Agent零基础学习路线,小白也能轻松掌握并收藏!

本文提供了一套2026年全新AI Agent零基础学习路线,强调从原理到实操、项目再到部署的循序渐进学习方式。文章指出,掌握AI Agent技能对于副业变现、简历加分、转行AI等非常有帮助,并针对学习中的常见误区提出了建议,如避免直接上框…

2026/10/9 10:24:48 阅读更多 →

最新新闻

水果识别深度学习实战:从数据到部署的工程落地指南

水果识别深度学习实战:从数据到部署的工程落地指南

简介:这是一套面向计算机相关专业在校学生、教师及初入行开发者的Python深度学习水果识别系统实战项目,专为毕业设计、课程设计与竞赛实践打造。项目基于经典CNN架构实现多类别水果图像分类,含完整可运行源码、详细项目说明文档及配套数据集&…

2026/10/11 1:00:13 阅读更多 →
TRISIS攻击安全仪表系统(SIS)的链路拆解与工控防护实践

TRISIS攻击安全仪表系统(SIS)的链路拆解与工控防护实践

1. 从一份缺失正文的分析报告说起:TRISIS到底特殊在哪工控安全圈子里,TRISIS这个名字不算陌生,但真正把它讲透的资料并不多。我最初接触这个样本是在一次内部技术复盘会上,当时拿到的材料只有一份标题和几页零散的IOC列表&#xf…

2026/10/11 1:00:13 阅读更多 →
对话式 Linux 运维 Agent:大模型驱动与高危操作人工确认设计实战

对话式 Linux 运维 Agent:大模型驱动与高危操作人工确认设计实战

我这人比较懒,尤其是碰上重复性的运维操作,能写脚本绝不动手。但脚本有个天生的短板:它只是个执行器,没有判断力。重启个服务、删个日志、改个配置,这些操作本身不难,难的是判断“现在能不能做”“做完之后…

2026/10/11 1:00:13 阅读更多 →
MySQL零基础入门:从安装建库到增删改查实战

MySQL零基础入门:从安装建库到增删改查实战

先交代一句:这篇东西不是照着官方文档念,而是按我自己带人入门的经验来写。很多新手学 MySQL 最大的问题不是学不会,而是被一堆命令吓住了,敲完也不知道自己在干嘛。这篇基础篇(一)会带着你把 MySQL 装好、…

2026/10/11 1:00:13 阅读更多 →
基于Qt与AI的黑白棋游戏源码解析:博弈树与剪枝实战

基于Qt与AI的黑白棋游戏源码解析:博弈树与剪枝实战

简介:一份基于Qt框架的黑白棋(翻转棋)完整游戏源码包,面向Qt入门者与游戏开发初学者,展示如何借助QWidget、QGraphicsView等组件从零搭建棋盘界面、交互事件与对战逻辑。资源共三十个文件,压缩包约一点四MB…

2026/10/11 1:00:13 阅读更多 →
Java SPI机制详解:从ServiceLoader到框架扩展原理

Java SPI机制详解:从ServiceLoader到框架扩展原理

参加Java面试时,如果对方问“你们怎么实现接口扩展”或者“框架为什么能自动加载实现类”,八成是想考察SPI机制。SPI全称Service Provider Interface,简单说就是Java原生的“插槽式”扩展机制:你定义一个接口,别人可以…

2026/10/11 0:59:12 阅读更多 →

日新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/11 0:00:27 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/11 0:00:27 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 5:23:50 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 21:32:20 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 10:38:42 阅读更多 →