缓存是数据的副本。只要同一份数据存在两处(DB + Cache),写入时就会有一个时间窗口,期间两处不一致。所以"缓存一致性"不是"消灭不一致"
所有缓存一致性方案,本质都是回答同一个问题:
数据变更后,如何让 Redis 的旧副本失效?
主要考虑 更新顺序 动作选择 失败兜底
主流答案有这些:
-
先写 MySQL,再写 Redis
-
先写 Redis,再写 MySQL
-
先删除 Redis,再写 MySQL
-
先写 MySQL,再删除 Redis
-
先删 Redis,再写 MySQL,再删 Redis(延时双删)
-
先写 MySQL,通过 Binlog 异步更新 Redis(最终一致性,高可用架构)
123 不行 456 行
1.先写 MySQL,再写 Redis

上图为 先写 MySQL 再 Redis,在高并发情况下 如果 A 写的时候 B 已经完成了数据的更新 那么 A 又改回去了就会出现上面的情况
这里需要读请求不更新 Redis
2. 先写 Redis 再写 MySQL

原理同图 1
3. 先删 Redis,再写 MySQL
此处的请求 A 为更新请求 但是 B 为读请求 会回写 Redis
请求 A 删除缓存的时候 更新比较慢,请求B 先进行了两步查询

4. 延迟双删
对与 4 再加了一层删除 网络上特别多人使用这个
但是我问 AI 各种都是不推荐 因为缓存等待时间无法掌握 只是把小窗口换成了更小的窗口而已

当 Redis 网络抖动、Redis 宕机、业务线程池满导致延迟任务被丢弃、连接超时,可能会出现双删失败,这是用线程池提交延迟任务,任务内部自带重试
TTL 兜底的话双删失败也可以保证不永久丢数据
这里不多置喙
为什么要删第一次

如果没有第一次删除,从"从写 DB"到"延迟任务执行"这 500ms 内 所有读请求都会命中旧缓存
第一次删除负责"及时性",第二次删除负责"兜底并发窗口
更推荐进行消息队列异步串行化删除。

异步删除对线上业务无影响,串行化处理保障并发情况下正确删除。
5.先写 MySQL,再删除 Redis
先删缓存的话,删完到 DB 提交完成之间,一个读请求会把旧值 重新加载进缓存 ------ 这就是著名的 "先删缓存"不一致 。反过来,先写 DB 再删缓存,读请求最多把尚未提交的旧值装进去,而这个窗口极短,且下一次写会再删一次。

对于第一次查询的情况,如果不是强一致性要求的业务,可以容忍
比如帖子,公告,短视频点赞数,用户头像,签名等等。

这种失败情况要同时满足
- 缓存刚好已失效
- 读请求 + 写请求并发
- 更新数据库 + 删除缓存的时间),要比读数据库 + 写缓存时间短
这明显不是很可能,因为写数据一般都加锁,绝大多数情况不可能比读数据库时间短
延迟双删变体-更删删
这部分既能解决主从复制的等待问题 也比延迟双删窗口更小 我觉得是当前最好的情况
先更新数据库 → 事务提交后删除缓存 → 等待一段时间 → 再次删除缓存

6. 先写 MySQL,通过 Binlog,异步更新 Redis
监听 MySQL 的 Binlog,然后通过异步的方式,将数据更新到 Redis,这种方案有个前提,查询的请求,不会回写 Redis。

订阅 MySQL 变更日志,只要这行数据发生更新,自动去删缓存。 哪怕业务代码里面根本没执行 DEL、代码 bug、消息丢了,binlog 一捕获变更,就去删缓存。很多大厂核心缓存用这个做终极兜底。
如果中途请求 B 需要查询数据,如果缓存无数据,就直接查 DB;如果缓存有数据,查询的数据也会存在不一致的情况。
7. 分布式读写锁(Redisson RReadWriteLock)
这是完全解决缓存一致问题的答案,缺点是吞吐量下降
写时阻塞读,读之间不冲突
几种方案比较
123 直接忽略
4.可行 但是复杂 还要引入消息队列 删更删的好处就算脏数据产生后有大概率情调脏 key 不需要 TTL 兜底
5.比较推荐 删除如果失败可以多试几次
此方案实时性最好,并发量也高 我觉得这的变体非常好比如(更删删策略)
6.对于多服务共同修改等,建议会用这种方式,纯高并发不使用 1
总结:
- 实时一致性方案:采用"先写 MySQL,再删除 Redis"的策略。
有主从 推荐用 更删删 策略
- 最终一致性方案 :采用"先写 MySQL,通过 Binlog,异步更新 Redis",可以通过 Binlog,结合消息队列异步更新 Redis,是最终一致性的最优解 。