缓存和数据库总不一致,先更库还是先删缓存?延迟双删到底靠不靠谱?
缓存与数据库不一致时,Cache Aside 模式下的默认顺序是先更新数据库,成功后再删除缓存;2026年的项目交付里更稳妥的做法是再叠加延迟双删与TTL兜底。先删缓存更容易让并发读把旧值回填,把不一致窗口拉长;先更库则能把问题集中到“缓存删除是否成功”这一个点上,后面可重试、可观测。
为什么默认先更库后删缓存,而不是先删缓存?
“先删缓存,再更新数据库”会制造一个缓存空窗:此时若有请求到达,发现缓存缺失,会回源数据库。如果数据库事务尚未提交,读到的是旧值,旧值随后被写回缓存。等到事务提交完成,缓存里可能已经是旧数据,只要这个 key 仍热,旧值就会被反复续命,脏数据窗口常见从几分钟到几小时,线上排查时常要靠清缓存应急。
反过来,先更新数据库再删除缓存,不一致窗口只存在于数据库提交后、缓存删除前这一小段时间。删除动作如果失败,问题也相对集中:TTL 到期后缓存自然失效,或者通过补偿任务补齐。相比“旧值提前回填”,这个顺序的系统性风险小一些。
- 常见坑:只删一次缓存不设 TTL,删除失败也无日志,最终只能靠用户投诉发现。
- 合格线:给依赖缓存的键都设过期时间;每一次删除失败都要有日志、计数并触发重试或告警。
延迟双删 + TTL:落地细节与经验区间
先更库后删缓存仍存在一个短窗口,若业务能接受最终一致,项目里常用“延迟双删 + TTL”覆盖它:
- 更新数据库前,先删除一次目标缓存;
- 在事务中更新数据库并成功提交;
- 等待一个较短延迟(经验区间 200~500ms),再删除一次缓存;
- 给目标键设过期时间(常见 300~900 秒),作为漏删时的最后兜底。
延迟时间要大于发生回填所需的时间,也就是“一次数据库读取 + 一次缓存写入”的耗时。在常见项目里,基础从 200ms 开始,若库上有慢查询,再往 500ms 靠。设置太短,第二次删除发生在旧值回填之前,等于白删;设置太长,不一致窗口也会被拉大。交付现场曾处理过一个后台配置同步:配置 key 读取量大、更新频率低,团队没有现成的 Binlog 消费链路。我们最初用“先删缓存再更新库”,结果新规则上线后接近一天才生效,用户反馈才让问题暴露。后来改成“先更库,延迟 400ms 双删(落在常见区间 200~500ms 内),TTL 统一设为 300 秒”,随后在测试环境做了并发读写模拟,脏数据窗口由小时级降到秒级。代价是多一次发布清理历史缓存和新增一条删除失败告警,但换来了更可控的一致性表现。
如果删除步骤经常失败,或同步流程里不允许等待,可以在延迟双删之上加异步补偿。下面是基于项目经验区间的方案对比,可作为业务决策参考,具体成本会因团队和技术栈不同而变:
- 方案A:同步删除 + 失败日志重试:改动量最小,性能好;但删除失败时不一致时间会更长。开发量一般不到 1 人日。
- 方案B:延迟双删 + TTL:能覆盖“并发回填旧值”的常见场景,运维上需要多控制一个延迟参数。开发量约 1~2 人日。
- 方案C:本地消息表或订阅 Binlog 补删:把删除动作从请求链路里拿出来,失败可自动重试,一致性更稳;本地消息表约 2~5 人日,Binlog 订阅在无现成组件时约 4~8 人日,并需要维护数据同步组件。
适用与不适用边界
这套方法适合读多写少、允许最终一致的业务,比如商品详情、配置表、列表聚合页。数据短暂为旧不会影响关键判断,且 TTL 到期后能从数据库重建缓存。
- 适用:缓存的读命中率较高,写操作比例不大;数据库事务能保证更新成功或回滚;缓存删除失败后有日志与重试入口。
- 不适用:账户余额、订单、库存等强一致状态。这些数据应以数据库事务和锁为准,不能把缓存当作业务判定依据;对它们做延迟双删,意义有限且增加复杂度。
- 不必上:并发读不高,数据库单次查询仅几毫秒时,先优化慢 SQL、加索引或合并请求更有效,而不是先引入一套缓存一致性框架。
此外,删缓存会让热点 key 出现空窗,瞬时大量请求可能直接打到数据库,即缓存击穿。顺序优化解决的是缓存与库的一致性问题,解决不了击穿;击穿要配合互斥回源、热点预热或限流,不能指望改个删除顺序就能消除。
怎样验收一致性方案是否合格
一致性方案不是“上线后没问题”就算完,建议把验收点写清楚:
- 写操作成功后,缓存最终会被删除或覆盖;删除失败时有日志、计数并自动重试。
- 数据库更新失败时,缓存保持原样,不做无意义删除。
- 在测试环境人为停掉缓存中间件几秒,再恢复,确认写入链路能重试补删。
- TTL 能在预期误差范围内兜底。TTL 太短会频繁回源,太长会拖长不一致,常见区间是 300~900 秒。
在 2026 年项目交付习惯中,把“每次缓存删除失败都能在监控中看到”作为上线条件,比临时依赖清缓存更接近合格线。
常见问题
延迟双删的延迟时间设多少?
常见经验区间是 200~500 毫秒,需大于“一次数据库读加一次缓存写”的耗时。慢查询较多时取上限一侧,不要直接套网上固定值。
删除缓存失败了怎么办?
记录日志并触发重试或告警,必要时用本地消息表或订阅 Binlog 补偿。只打日志不重试,等于把一致性交给 TTL 的运气。
缓存击穿了,能靠调整删除顺序解决吗?
不能。顺序只解决缓存与数据库的最终一致,击穿要配合互斥回源、分布式锁或热点预热,并控制回源并发。
库存、余额这类强一致数据能加缓存吗?
不建议把核心状态直接放缓存,更不能把缓存当作扣减或支付依据。数据库事务与锁才是可靠防线,缓存只能做只读加速。
如果确定要上缓存,先明确业务允许的不一致窗口,再选择“先更库、延迟双删、TTL 兜底”;强一致场景对着一份数据库查询即可,不要用缓存方案增加自己的负担。
-
上传目录跟着代码一起发版,图片全丢了,文件到底该存本地还是对象存储?
日期:2026年9月13日 阅读:101
-
刚保存完跳详情页,读到的还是旧记录,读写分离是不是都得走主库?
日期:2026年9月12日 阅读:44
-
定时任务单机跑得好好的,一上多台机器就重复执行,到底该从哪拦?
日期:2026年9月11日 阅读:33
-
表刚上线时用自增主键挺省事,真到分库那天改起来有多麻烦?
日期:2026年9月10日 阅读:73
-
调大数据库连接池后接口反而变慢,连接数到底设多大合适?
日期:2026年9月9日 阅读:87




