先更库还是先删缓存?数据库与 Redis 双写一致性全对比
2026/6/22 23:50:09 网站建设 项目流程

先更库还是先删缓存?数据库与 Redis 双写一致性全对比

这个问题几乎每个后端都踩过坑。

答案看似简单,实则藏着极端场景下的致命 bug。


核心矛盾:为什么需要"双写"?

因为数据库和 Redis 的角色不同:

角色职责
MySQL最终数据源,保证持久化和事务
Redis热点缓存,加速查询

读多写少时,数据同步路径是:写 DB → 删/更缓存 → 下次读命中缓存

问题就出在这个箭头上:顺序反了,数据就脏了。


方案一:先更库,后删缓存(主流推荐 ✅)

这是大多数公司的默认选择。

流程

① 更新 MySQL ② 删除 Redis 缓存 ③ 下次读 → 缓存未命中 → 回源查 DB → 写入缓存

为什么推荐?

因为删缓存比更新缓存安全

  • 删缓存:最坏结果是缓存短暂不存在,读请求回源一次,数据最终一致
  • 更新缓存:如果更新失败,缓存里存的是旧数据,用户永远拿不到新值

但有一个致命场景:延时双删都救不了

时间线: T1: 线程A 更新 DB(新值 = 100) T2: 线程B 读缓存 → 命中旧值(值 = 50) T3: 线程A 删缓存 T4: 线程B 旧值已读走,返回 50 ❌

问题本质:更新 DB 和删缓存之间存在时间差,这段窗口内,旧读请求可能恰好命中缓存。

这不是概率问题,高并发下一定会发生

怎么解决?三种手段

手段原理效果
延时双删更新 DB 后,延迟 N ms 再删一次缓存兜底,但 N 难设定
串行化同一 key 的读写加分布式锁强一致,但牺牲性能
消息队列更新 DB 后发 MQ,异步确保删缓存解耦,但引入最终一致性

其中消息队列方案是大厂最常用的:

更新 DB → 写 Binlog → Canal 订阅 → 发送 MQ → 消费删缓存

Canal 把"删缓存"这个动作从业务代码中剥离,即使删失败,MQ 会重试,保证最终一定删掉。


方案二:先删缓存,后更库(绝对不推荐 ❌)

流程

① 删除 Redis 缓存 ② 更新 MySQL ③ 下次读 → 缓存未命中 → 回源查 DB → 写入缓存(新值)

看起来也能保证最终一致?

看这个场景

时间线: T1: 线程A 删缓存 T2: 线程B 读缓存 → 未命中 → 查 DB(此时 DB 还是旧值) T3: 线程B 把旧值写入缓存 T4: 线程A 更新 DB(新值 = 100) 结果:缓存 = 旧值,DB = 新值,数据永久不一致 ❌❌❌

这个 bug 比方案一严重得多

对比项先更库后删缓存先删缓存后更库
脏数据持续时间短暂(下一次读就修复)永久(直到缓存过期或手动清理)
发生概率高并发下必现较低,但一旦发生就是脏数据
修复成本自动修复需要人工介入或等待过期

先删缓存的最大风险是:在 DB 更新完成前,旧值已经被写回缓存了。

一旦发生,缓存里的旧值会一直存在,直到 TTL 过期。如果 TTL 设得很长(比如 1 小时),这 1 小时内所有读请求都拿到脏数据。


两种方案终极对比

维度先更库后删缓存 ✅先删缓存后更库 ❌
脏数据窗口极短(μs~ms 级)可能很长(直到 TTL 过期)
脏数据能否自愈✅ 能(下次读自动修复)❌ 不能(旧值已写入缓存)
实现复杂度中等(需处理延时双删或 MQ)简单但风险极高
大厂实践✅ 主流方案❌ 基本不用
推荐指数⭐⭐⭐⭐⭐

真正的最优解:不要自己写双写逻辑

最高效的做法是让基础设施替你完成

方案工具原理
Binlog 异步删除Canal + MQ监听 DB 变更,异步删缓存,失败重试
订阅 Binlog 直写Otter / Maxwell变更直接同步到 Redis,不经过业务代码
缓存中间件JetCache / Cache Aside 框架封装双写逻辑,内置重试和补偿

核心思想一致:把"删缓存"从业务主流程中剥离,用异步 + 重试保证最终一致性。


一句话总结

先更库,后删缓存。不是因为它完美,而是因为它的最坏情况只是"短暂不一致",而反过来的最坏情况是"永久脏数据"。

能用 MQ 异步删,就别在主链路上同步删。能让 Canal 干的活,就别让业务代码扛。

双写一致性的本质不是选顺序,而是承认一定会不一致,然后设计一个能自愈的机制

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询