延迟双删这个名字,后端面试题里出现的频率,快赶上“缓存穿透怎么解决”了。但说句实话,我这几年前前后后排查线上缓存脏数据,真正把这个方案用对的人不多。常见的做法是:代码里写一个sleep(200),然后删两次缓存,面试时能背出流程,可真被问到“延迟时间到底该设多少”“第二次删失败怎么办”“主从延迟下这方案还成不成立”这类问题,大多数人就会卡壳。
这篇文章不打算复述教科书里的定义,我想换个角度,专门聊清楚延迟双删的适用边界和落地细节。过程里会带上我实际改过的项目、踩过的坑,以及最终在什么场景下放弃它、换成了什么更稳的方案。如果你正在做缓存与数据库一致性相关工作,或者正被这类面试题折磨,这篇应该能给你提供一些书本之外的参考。
1. 延迟双删的来龙去脉:从一个经典竞态说起
1.1 三种缓存更新模式的取舍
聊延迟双删之前,得先对齐一个前提:我们现在用Redis做缓存,绝大多数场景走的是Cache Aside Pattern,也就是旁路缓存模式。这个模式的行为很直观:读请求先查缓存,没命中就去数据库查,查到后回填缓存;写请求先更新数据库,然后把对应的缓存删掉。这套流程看起来简单,但它能成立的前提是“缓存里的数据过期了没关系,反正下一次读会回填最新值”。
和Cache Aside并列的还有另外两种思路。Read/Write Through是把缓存当成存储的代理,业务层不直连数据库,所有读写请求都先经过缓存组件,由它来保证一致性;Write Behind则是先写缓存,然后异步批量刷到数据库,性能很好,但存在数据丢失风险。这两种模式实现成本高,适合缓存组件已经演化成中间件级别的场景,业务侧直接写自己的项目时很少采用。
所以大部分人的日常就是Cache Aside。既然大家都用旁路缓存,那把它的写路径研究透彻就有实际意义。我见过很多团队在代码评审阶段争论“应该先删缓存还是先更库”,其实这个问题的答案早已明确:先更新数据库,再删除缓存,尽量避免先删缓存再更新数据库的顺序。
1.2 先更库再删缓存为什么还不够稳
那“先更新数据库,再删除缓存”是不是就绝对安全?正常串行请求下确实稳,可一旦并发上来,就会出现一个很隐蔽的窗口。我画过无数次时间线,这里用一个具体例子说明:
- 线程A更新数据库,把商品库存从100改成90;
- 线程B发起读请求,此时缓存还没被删除,读到了100这个旧值,返回给用户;
- 线程A随后删除缓存;
- 后续读请求重新回填90,数据恢复一致。
这个窗口期很短,而且以“最终一致”的标准来看,影响不大。真正要命的是另一个时序:线程A先删除了缓存,然后准备更新数据库,结果线程B趁这个间隙从数据库读到了旧值100,回填到缓存。由于线程A的数据库更新还没完成,这个旧值可能要在缓存里存很久。为了对付这个问题,才有了“延迟双删”的提法。
延迟双删的思路并不复杂,本质上是做两次失效操作:第一次删除发生在更新数据库的前后,目的是让后续读请求尽量走数据库;第二次删除放在延迟一段时间之后,目的就是清理掉第一个时间窗口里可能被回填的旧值。严格来说,“延迟双删”这个名字有点误导,把它理解成“补偿性二次失效”会更准确。
这里有个关键点必须强调:延迟双删解决的是“删除缓存后、更新数据库前,中间被回填旧值”的竞态,它不代表所有缓存一致性问题都能解决。很多人拿它当万能药,结果在强一致场景里照样翻车,原因就在边界没划清。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 延迟双删的适用边界:别一杆子套到所有缓存场景
2.1 判断适不适合,先看这四个维度
我在项目里判断一个业务能不能用延迟双删,不会先想怎么写代码,而是先看四个维度。第一是一致性要求,如果业务允许秒级甚至百毫秒级的不一致,延迟双删才能有生存空间;如果要求强一致,也就是任何时刻读到的东西都必须是最新值,那延迟双删直接出局。第二是读写比例,这个方案比较适合读多写少的场景,因为写操作少,二次失效的代价被摊薄了。第三是写并发度,如果同一个key的并发写非常密集,每次写都带来两次删除和一个等待周期,缓存会被打得千疮百孔。第四是基础设施情况,单机Redis、主从延迟可控的环境里,延迟双删还
