先更新数据库还是先删缓存

先更新数据库还是先删缓存

核心争论

在缓存与数据库的一致性方案中,争议最大的问题是:更新操作时,应该先操作数据库,还是先操作缓存?

这里有两种主要的操作:删除缓存更新缓存。组合起来有四种可能的执行顺序:

顺序 策略 推荐度
先删缓存,再更新 DB Delete Cache First ⭐⭐
先更新 DB,再删缓存 Delete Cache After DB ⭐⭐⭐⭐
先更新 DB,再更新缓存 Update Cache After DB ⭐⭐
先更新缓存,再更新 DB Update Cache First

方案一:先删缓存,再更新数据库

执行流程

写操作:
1. 删除缓存中的 Key
2. 更新数据库

读操作(在步骤 1 和 2 之间发生):
1. 缓存未命中
2. 查询数据库(拿到旧数据)
3. 将旧数据写入缓存  ← 缓存中写入了旧数据!

问题:读写并发导致不一致

时间线:
T1: 写线程 → 删除缓存(成功)
T2: 读线程 → 缓存未命中 → 查数据库(拿到旧数据 V1)
T3: 写线程 → 更新数据库(V1 → V2)
T4: 读线程 → 将旧数据 V1 写入缓存

结果:缓存中是 V1(脏数据),数据库中是 V2(新数据)

适用场景

  • 读多写少且写操作立刻发生时读请求概率低的场景
  • 可以容忍短暂不一致
  • 配合”延迟双删”使用

方案二:先更新数据库,再删缓存

执行流程

写操作:
1. 更新数据库
2. 删除缓存中的 Key

读操作(在步骤 1 和 2 之间发生):
1. 缓存命中(缓存中还是旧数据?不,但读操作拿到的是旧数据)
   → 不对,让我重新分析

问题场景分析

时间线(先写 DB 再删缓存):
T1: 写线程 → 更新数据库(V1 → V2)
T2: 写线程 → 删除缓存
T3: 读线程 → 缓存未命中 → 查数据库(拿到 V2)→ 写入缓存

但有一个极低概率的并发问题:

T1: 读线程  缓存未命中  查数据库(拿到 V1
T2: 写线程  更新数据库(V1  V2
T3: 写线程  删除缓存(此时缓存中还没有数据)
T4: 读线程   V1 写入缓存

结果:缓存中是 V1(脏数据)

但这种情况要求:
1. 缓存刚好过期(缓存 miss)
2. 读请求在数据库查询完成之前触发写操作
3. 读请求先于写请求的”删缓存”操作完成

实际发生概率极低,需要满足的条件非常苛刻。

为什么不先更新缓存

如果改为”先更新数据库,再更新缓存”:

T1: 写线程 A  更新数据库(V1  V2
T2: 写线程 B  更新数据库(V2  V3
T3: 写线程 B  更新缓存(V3
T4: 写线程 A  更新缓存(V2    B 的更新覆盖了!

结果:数据库中是 V3,缓存中是 V2

先更新缓存的风险:多个并发写操作可能导致缓存的最终值和数据库不一致。这就是为什么删除缓存比更新缓存更安全——下次读操作会重新加载最新值。

方案三:先更新缓存,再更新数据库(不推荐)

写操作:
1. 更新缓存(成功)
2. 更新数据库(失败)

结果:缓存中是 V2,数据库中是 V1 → 永久不一致

这是最危险的方案——数据库也可能失败。一旦缓存在先更新成功而数据库更新失败,就会永久不一致。而且由于缓存中没有过期时间或过期时间较长,问题可能持续很久。

为什么推荐”先更新 DB,后删缓存”

理由一:数据库操作更可靠

数据库支持事务,可以回滚。先操作数据库,如果失败可以回滚或重试。而缓存操作通常不考虑回滚。

理由二:不一致窗口最小化

先删缓存再更新 DB:
不一致窗口 = 从删缓存到更新 DB 的时间(可能很长)

先更新 DB 再删缓存:
不一致窗口 = 从删缓存到下次读请求重建缓存(缓存 miss 的瞬间)

理由三:极端情况的概率极低

前面分析的那个极低概率的并发问题,在实际生产环境中的发生概率非常小,通常可以忽略。

理由四:符合”最终一致性”要求

即使发生了短暂的不一致,缓存被删除后,下一次读取会从数据库加载最新数据,自动恢复一致性。

异常处理

缓存删除失败怎么办

不管是先删还是后删,如果删除缓存的操作失败了,都会导致不一致。

解决方案:删除重试机制

1. 更新数据库
2. 删除缓存
3. 如果删除失败  将删除任务放入消息队列
4. 通过消费者异步重试删除

或者使用 canal + MQ 的 Binlog 监听方案:

MySQL Binlog → Canal → Kafka → 消费者 → 删除缓存

总结

各种策略的合理性排序:

先更新 DB 再删缓存  >  先删缓存再更新 DB(需配合延迟双删)  >  先更新 DB 再更新缓存  >  先更新缓存再更新 DB

在实际工程中,先更新数据库,后删除缓存是最推荐的方案,也是 Cache Aside 模式的标准做法。它虽然在极端并发下存在微小的不一致可能,但概率极低,且配合缓存的过期时间可以自动恢复。如果有更高的一致性要求,可以加上延迟双删Binlog 监听作为补充。

© 版权声明
THE END
喜欢就支持一下吧
点赞10 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容