Redis 和 MySQL 缓存一致性问题——别让你的缓存“背叛”你!

136 阅读5分钟

亲爱的小伙伴们:

有没有过这样的经历?你明明把数据库更新了,可用户刷出来的页面还是老数据!这就好比你明明换了女友,朋友还在社交平台@你和前任的照片。🙃

这,就是Redis 缓存与 MySQL 数据库一致性问题——缓存,可能会“背叛”你!


一、一致性种类,先分个类

在正式讲缓存一致性之前,咱们得先整明白“一致性”到底分几种:

一致性类型定义举个栗子
强一致性写入成功后,所有读请求立刻能读到最新值银行转账、支付
弱一致性写入成功后,读请求可能能读到最新值一般业务场景
最终一致性写入成功后,经过一段时间,读到的一定一致微博点赞、浏览量、缓存系统

✅ 关键认知:

  • 强一致性 → 读写“实时一致”
  • 最终一致性 → 短时间不一致可以接受,但最终肯定一致

二、Redis + MySQL 到底是什么一致性?

答案是:

只能做到“最终一致性”

为什么?

  • MySQL:强一致(事务ACID保证)
  • Redis:无事务、异步、非阻塞 → 快速但不保证一致性
  • Redis + MySQL:分布式异构系统 → 没有全局事务 → 只能靠“补救”保证最终一致

✅ 你写库 → 删缓存 → 这两步不是原子操作 → 并发下没法100%保证实时一致。


三、为什么会不一致?

Redis 和 MySQL 本质上是“两套世界”——

  • Redis:我快!我轻!我不靠谱!
  • MySQL:我慢!我稳!我靠谱!

当你执行:

  1. 写 MySQL(更新数据)
  2. 删除 Redis 缓存

看似合理,但并发一来:

  • 线程A:删缓存 → 正在写MySQL
  • 线程B:查缓存→发现没了→查库(读到旧值)→写回Redis!

🙃 于是——你刚更新的数据,被另一个并发“复原”了,缓存回滚成了老数据!


四、缓存双删:先砍一刀,再补一刀

缓存双删的思路很简单

  1. 先删缓存(防止并发读老数据)
  2. 写 MySQL
  3. 再删缓存一次(防止并发把旧数据写回缓存)
redis.delete("user:123");       // 第1刀
userMapper.update(user);          // 写库
Thread.sleep(50);                 // 等一等
redis.delete("user:123");       // 第2刀

✅ 好处:实现简单,不需要额外中间件。
❌ 坏处:sleep时间不可控,并发量大时没法保证真的删得干净。


五、延迟双删:扔个炸弹,晚点炸

延迟双删 = 缓存双删 + 延迟异步清理

  1. 删除缓存
  2. 写 MySQL
  3. 发消息(MQ、延时任务)
  4. 延迟几百毫秒 → 消费消息 → 再删缓存
redis.delete("user:123");
userMapper.update(user);
mq.send("delete-cache", user.getId());

// 消费端
Thread.sleep(200);
redis.delete("user:123");

✅ 好处:

  • 非阻塞,不卡主线程
  • 延迟时间灵活调整
  • 高并发更稳

❌ 坏处:

  • 需要MQ支持
  • 系统复杂度提高

六、其他常用一致性方案(加餐时间)

除了缓存双删和延迟双删,还有这些常见方案:

策略描述优点缺点
Cache‑Aside(先更新 DB 再删缓存)写入数据库后删除缓存,下次读触发缓存重建简单,广泛使用;删除后新读自动刷新缓存 (yunpengn.github.io)删除失败会导致长期脏缓存;短时间内缓存命中率下降
异步删除 + 重试机制写 DB 后异步发送删除操作(消息队列),失败可重试提高删除可靠性,侵入式较小;适合高可用场景引入 MQ 和重试逻辑,复杂度上升
Binlog 驱动缓存失效订阅 MySQL binlog(如 Canal),解析写操作后清缓存几乎不改业务逻辑,最终一致;可处理 DB-master/slave 延迟需额外组件(Canal),处理顺序与延迟问题
写入缓存然后异步写 DB(Write‑Behind)写先写 Redis,再异步写入 DB写快,读性能佳;支持批量 / 顺序写入可能丢数据,顺序错乱,设计复杂
写穿/写直达(Write‑Through/Write‑Behind)写请求通过缓存统一,缓存同步写数据库(同步或异步)提高一致性,操作统一Redis 不是持久存储;失效恢复麻烦,性能受限
读穿(Read‑Through)读取由缓存自动去 DB 填充对应用透明,无需手动缓存逻辑Redis 默认不支持;需插件或中间层
逻辑删除 + 异步删除缓存DB 标记为删除,最终由异步流程清缓存写操作快,对缓存命中影响小多线程协调复杂,需额外逻辑支持
短过期时间 + 母鸡式双删缓存大幅缩短 TTL,写 DB 后删除缓存简单稳定,过期兜底;降低脏数据持续时间缓存命中率下降,读频繁命中 DB,增加压力
分布式锁 + DB→缓存更新写 DB 后获取锁更新缓存,避免并发写混乱并发安全,减少缓存抖动增加延迟,分布锁成本,性能下降

不同场景、不同权衡,合理选择。


七、总结发布版:别让缓存“背叛”你

  • 缓存双删:同步+两次删除,简单有效但不精准
  • 延迟双删:异步+延迟删除,性能更优雅
  • 其他方案:读写穿透、版本控制、主库强读

一致性本质

  • Redis + MySQL → 只能做到最终一致性
  • 真正的强一致性 → 需要事务、同步锁、或者牺牲性能

现实里:

  • 支付、金融 → 强一致性
  • 社交、电商、内容 → 最终一致性

缓存就像爱情——太快的不一定靠谱,慢一点的反而更牢靠。

不追求完美,但追求“最终正确”。别让你的缓存,把你坑了!😄