Redis 和 MySQL 双写一致性:延迟双删、读写锁、MQ、Canal 怎么选?

Redis 做缓存时,最经典的读流程是:先查 Redis,命中直接返回;未命中再查 MySQL,然后把结果写回 Redis。

真正麻烦的是写操作。只要 MySQL 的数据变了,Redis 里的旧缓存就可能变成脏数据。所以"Redis 和 MySQL 双写一致性"本质上是在回答一个问题:数据库更新后,缓存到底怎么处理,才能尽量不读到旧数据?

一、先删缓存,还是先改数据库?

常见思路有两个:

  1. 先删除缓存,再修改数据库。
  2. 先修改数据库,再删除缓存。

先看"先删缓存,再改数据库"的问题:
MySQL Redis 读线程 写线程 MySQL Redis 读线程 写线程 删除缓存 查询缓存,未命中 查询旧数据 把旧数据写入缓存 更新数据库为新数据

最终结果是 MySQL 已经是新数据,但 Redis 被读线程写回了旧数据,缓存和数据库不一致。

更推荐的基础方案是:先更新数据库,再删除缓存
MySQL Redis 读线程 写线程 MySQL Redis 读线程 写线程 更新数据库 删除缓存 下次查询缓存未命中 读取新数据 写入新缓存

这并不能做到绝对强一致,但发生脏数据的概率更低,是工程里更常见的选择。

二、延迟双删

课件里重点讲了"延迟双删"。它的流程是:
删除缓存
修改数据库
延迟一小段时间
再次删除缓存

为什么要删两次?因为第一次删除是为了让后续读请求不能继续读旧缓存;第二次删除是为了兜住并发读线程把旧数据重新写回缓存的情况。

为什么要延迟?因为在主从数据库架构下,写入主库后,从库可能还没同步完成。如果读线程从从库读到了旧数据,并把旧数据写入 Redis,第二次延迟删除就能把这份脏缓存清掉。

延迟时间没有固定答案,通常要结合业务读写耗时、主从同步延迟来估计。它的缺点也很直接:代码耦合度高,而且仍然有短暂脏数据风险。

三、强一致场景:读写锁

如果业务对一致性要求很高,比如优惠券库存、余额、订单状态,就不能只依赖延迟双删。

这时可以使用 Redisson 的读写锁:

锁类型 作用
读锁 readLock 多个读线程可以共享
写锁 writeLock 写线程独占,会阻塞其他读写

读数据
加读锁
读缓存或数据库
释放读锁
写数据
加写锁
更新数据库
删除缓存
释放写锁

读写锁的优点是强一致性更好;缺点是性能较低,因为写操作会阻塞读操作,适合一致性优先的关键业务。

四、最终一致场景:MQ 异步通知

如果业务可以接受短时间延迟一致,比如文章热点数据、商品详情页、首页推荐数据,就可以把缓存更新动作异步化。
item-service 更新 MySQL
发布 MQ 消息
cache-service 监听消息
删除或更新 Redis 缓存

这个方案的核心是"数据库写成功后发消息,缓存服务消费消息后更新缓存"。它降低了业务代码和缓存逻辑的耦合,但必须保证 MQ 的可靠性,否则消息丢失会导致缓存长期不一致。

五、无侵入方案:Canal 监听 binlog

Canal 的思路更进一步:业务服务只负责写 MySQL,不直接关心缓存。Canal 伪装成 MySQL 的从节点,监听 binlog,再通知缓存服务更新 Redis。
item-service
写 MySQL
binlog
Canal 监听变更
cache-service 更新 Redis

binlog 会记录 DDL 和 DML 语句,不记录 SELECT 这类查询语句。Canal 基于 MySQL 主从同步机制读取变更,因此对业务代码侵入较小。

六、怎么选?

业务类型 推荐方案
文章、商品详情、热点数据 MQ 或 Canal,追求最终一致
库存、余额、抢券 Redisson 读写锁,追求强一致
简单缓存场景 先更新数据库,再删除缓存
需要兜底并发脏写 延迟双删

面试回答可以这样收尾:

我会先说明业务的一致性要求。如果是允许延迟一致的热点数据,我更倾向于 MQ 或 Canal 异步删除缓存;如果是库存这类强一致业务,我会使用 Redisson 读写锁来控制并发读写。普通缓存更新则采用先更新数据库再删除缓存,必要时配合延迟双删降低脏数据概率。

相关推荐
罗超驿2 小时前
9.深度剖析MySQL约束的工程设计:自增主键的分布式局限、外键约束的权衡,与CHECK的版本适配实践
数据库·mysql
Kiyra2 小时前
Agent 的记忆不是存数据库就行:上下文预算与轻量记忆的设计实战
数据库·人工智能·后端·面试·职场和发展·哈希算法
jiayong232 小时前
MySQL 8.0 数据库恢复问题完整解决方案
数据库·mysql
期待のcode2 小时前
Redis数据类型
运维·数据结构·redis
czlczl200209252 小时前
普通索引和唯一索引 查询性能差异
数据库
@小柯555m2 小时前
MySql(正则表达式--电话号码格式校验)
数据库·sql·mysql·正则表达式
van久2 小时前
Day29:Redis 缓存实战
数据库·redis·缓存
.柒宇.2 小时前
Redis哨兵模式详解
数据库·redis·bootstrap