Redis 和 MySQL 缓存一致性,延迟双删为什么不是强一致

Redis 和 MySQL 缓存一致性,延迟双删为什么不是强一致

Redis 放在 MySQL 前面,读请求是快了,但"数据库更新成功,缓存是不是也一定是新值"并没有答案。

我更愿意把缓存当成数据库的副本,而不是第二个事实源。这样遇到故障时,先问三个问题:旧值最多能存在多久,删除失败怎么补,哪些判断根本不能读缓存。

先看最容易漏掉的竞态

假设缓存里有 V1,现在写请求 B 把数据库改成 V2。

常见的 Cache-Aside 写法是:

text 复制代码
1. 提交 MySQL 事务
2. 删除 Redis key
3. 下一次读请求从 MySQL 回源,再写入 Redis

问题在第 2 步和第 3 步之间。读请求 A 可能已经拿到了旧数据,或者刚好从一个还没追上的只读副本读到了 V1。A 随后把 V1 写回 Redis,最后就变成:

text 复制代码
MySQL = V2
Redis  = V1

这不是"Redis 删除命令偶尔慢"这么简单,而是读写并发下的顺序没有被约束。

延迟双删到底解决哪一段

有人会这样写:

java 复制代码
updateDatabase();
deleteCache();
sleep(500);
deleteCache();

第二次删除确实能覆盖一部分场景:读请求先拿到旧值,第一次删除后回填,第二次删除再把它清掉。

但这个方案有几个前提,少一个都不能把它叫成"强一致":

  • 延迟必须大于一次慢读、序列化和回填的总耗时。固定写死 500 毫秒只是猜测,线上 P99 变了就会失效。
  • 第二次删除本身可能失败。失败只打日志,缓存不会因为你"计划删过"就自动消失。
  • 回源如果走延迟副本,副本还没追上时读到的仍然是 V1。这时再删一次,也只是把错误窗口往后推。
  • sleep 会占住业务线程,流量一高,缓存问题还没解决,线程池先被拖慢了。

所以延迟双删更像是给历史系统加的一层概率缓解,适用于允许短时间旧读的场景。库存、余额、权限判定这类数据,不能靠它做最终判断。

更稳的落地顺序

我一般按下面的边界拆:

1. 数据库事务提交后再删缓存

不要在事务还没提交时就删缓存。事务回滚了,缓存反而先没了;这虽然最终可能回源修复,但会制造不必要的抖动。

写路径可以先保持简单:

text 复制代码
提交 MySQL
  ↓
删除 Redis
  ↓
删除失败进入持久重试

重试记录要有业务 key、版本或事件 id,不能只靠内存里的一个异步任务。进程重启、队列积压、Redis 短暂不可用时,都要能继续处理。

2. 读请求要认识"副本不新鲜"

如果数据库有只读副本,缓存 miss 后直接读副本,再回填缓存,旧值写回的窗口会更大。

可以选一种明确的策略:

  • 写入后的短窗口内,回源读主库。
  • 带上最低版本号,副本没追上就改读主库。
  • 复制延迟超过阈值时,暂停这类数据的缓存回填。

这里的重点不是把所有请求都打到主库,而是别把"副本可读"误当成"副本最新"。

3. 写入口多时,用事件做收敛

如果更新不只来自一个服务,应用层删缓存很容易漏。MySQL 的 binlog 能按提交顺序记录事务变更,可以用 CDC 或消息链路消费它,再做缓存失效。

这条链路也不是魔法,至少要处理:

  • 消息重复:同一个事件重放不能把数据搞坏。
  • 消息乱序:旧事件不能覆盖新版本。
  • 消费失败:要有重试、死信和对账。
  • 延迟监控:看"数据库提交时间到缓存失效时间"的 P95/P99,而不是只看消费成功数。

缓存一致性不是一个"选哪种方案"的单选题。真正要定的是:数据库是事实源,缓存允许多旧,失效事件多久必须收敛,以及哪些请求永远不能用缓存结果做最后决定。

面试时我会这样回答

如果只剩一分钟,我会先说:

Redis 是 MySQL 的副本,不是共同事务的一部分。提交数据库后删除缓存是常见的 Cache-Aside 写法,但删除失败、慢读回填、只读副本延迟都会造成短暂不一致。延迟双删只能缩短部分竞态窗口,不能保证强一致。重要数据要用主库或一致读,删除失败要持久重试,多写入口可以用 binlog CDC 做最终收敛。

面试官继续追问"那到底多久删第二次",我不会报一个看起来很精确的数字,而是先问业务的读 P99、复制延迟和允许的旧读时长。这个数字应该从监控里量出来,不是从博客里抄出来。

相关推荐
添砖java‘’2 小时前
Redis中常用数据结构
数据库·redis·缓存
这个DBA有点耶3 小时前
数据库迁移灰度验证:从读流量到双写再到全量切换的完整路径
数据库·mysql·架构
ruleslol4 小时前
redis的持久化
redis
01_ice7 小时前
MySQL事务
数据库·mysql
玛丽莲茼蒿8 小时前
Redis(六)—— Redis高级
数据库·redis·缓存
渣渣盟8 小时前
Flink 写入 Redis 实战:数据模型选型、连接池调优与常见坑
大数据·redis·flink
旺旺仙贝呦9 小时前
MySQL__MongoDB__Redis 入门
redis·mysql·mongodb
陈聪.9 小时前
企业级 NoSQL 数据库 Redis 核心知识整理
数据库·redis·nosql
隔窗听雨眠10 小时前
MySQL索引机制深度解析:从B+树原理到高并发查询优化的完整指南
数据库·b树·mysql