延迟双删在读写分离场景下不可靠,因其第二次删除无法清除其他实例从从库读到并写入的旧数据,且删除失败无重试;而Redis缓存标记法通过写时设标志、读时判标志直连主库,容错性更高。

主从延迟导致读请求穿透到从库,拿到旧数据
主从同步不是实时的,哪怕网络良好,也可能有几十毫秒到几百毫秒的 lag。当业务走读写分离(写主库、读从库),又依赖 Redis 缓存兜底时,就容易在“缓存失效 → 查从库 → 写入旧数据到缓存”这个链路里埋下不一致隐患。
典型现象:SELECT * FROM user WHERE id = 123 在从库返回了 5 分钟前的头像 URL,而主库早已更新;该结果被写回 Redis,后续请求全读到错误值。
关键点在于:缓存层无法感知从库数据是否已同步,它只管“没命中就查 DB”,而 DB 此时是旧的。
为什么不能靠“查完从库再比对时间戳”来兜底
有人想在从库查出记录后,用 updated_at 字段和 Redis 里存的更新时间做比对,相等才写缓存——这思路看似合理,但实际踩坑不少:
- 主库写入时若未显式设置
updated_at = NOW()(比如 ORM 自动生成或使用默认值),从库的updated_at可能根本没变 - 高并发下多个写请求挤在 1ms 内,数据库事务提交顺序和 binlog 写入顺序可能不一致,导致从库回放后
updated_at相同但内容不同 - MySQL 的
ROW格式 binlog 不带时间戳,从库回放时靠的是事件顺序,不是时间戳值
所以靠字段比对判断“是否最新”,在真实生产环境并不可靠。
Redis 缓存标记法怎么真正起作用
有效做法是把“某条记录刚被主库修改过”这件事,显式地广播出去,让读请求能感知并主动降级到主库。核心是用 Redis 做一个轻量级的“写标记”:
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
写操作时:
– 更新主库 user 表 ID=123 的记录
– 同时执行 SETEX cache_flag:user:123 2000 "1"(2000ms 是预估主从延迟上限)
读操作时:
– 先 EXISTS cache_flag:user:123
– 若存在,跳过缓存,直连主库查 SELECT * FROM user WHERE id = 123
– 若不存在,才走常规缓存逻辑(查 Redis → Miss → 查从库 → 回填)
注意:cache_flag 的过期时间必须略大于实际主从延迟(建议用 SHOW SLAVE STATUS 中的 Seconds_Behind_Master 峰值 + 200ms 安全余量),否则标记失效太快,起不到兜底作用。
延迟双删不适合读写分离场景
很多人提“先删缓存 → 写 DB → 延迟 X 秒 → 再删缓存”,这个方案在单库直连时能缓解部分并发问题,但在读写分离架构下会失效:
因为第二次删除发生在写请求线程内,它删的是当前节点看到的缓存;而读请求可能正在另一个服务实例里,刚从从库读到旧数据并写入本地 Redis 或共享缓存,此时删不掉。
更严重的是:如果第二次删除失败(如 Redis 网络抖动),整个兜底机制就断了,且无重试保障。相比之下,标记法是幂等写入 + 自动过期,容错性高得多。
真正难处理的,其实是那个“预估延迟值”——它得随主从负载动态调整,静态配成固定值,要么兜不住,要么过度降级拖慢读性能。

















