MySQL 与 Redis 如何保证双写一致性
前言
在分布式环境下,要实现强一致性(在任何时刻读取的数据都是最新的)是极其困难且代价高昂的,通常会严重牺牲性能。因此,在实践中,我们通常追求最终一致性,即允许在短暂的时间内数据不一致,但通过一些手段保证数据最终会保持一致。
一、基础概念:为什么会有不一致?
在一个包含 MySQL(作为可靠数据源)和 Redis(作为缓存)的系统中,所有的写操作(增、删、改)都必须同时处理这两个地方。

这个过程中,任何一步失败或延迟都会导致不一致:
- 写 MySQL 成功,写 Redis 失败:导致 Redis 中是旧数据。
- 写 Redis 成功,写 MySQL 失败:导致 Redis 中是'脏数据',数据库中不存在。
- 并发读写:一个线程在更新数据库,但还没更新缓存时,另一个线程读取了旧的缓存数据。
二、核心策略与模式
解决双写一致性有多种策略,我们需要根据业务场景(对一致性的要求、读写的比例等)进行选择。
策略一:Cache-Aside Pattern(旁路缓存模式)
这是最常用、最经典的缓存模式。核心原则是:应用程序直接与数据库和缓存交互,缓存不作为写入的必经之路。
-
读流程:
- 收到读请求。
- 首先查询 Redis,如果数据存在(缓存命中),直接返回。
- 如果 Redis 中没有数据(缓存未命中),则从 MySQL 中查询。
- 将从 MySQL 查询到的数据写入 Redis(以便后续读取),然后返回数据。
-
写流程:
- 收到写请求。
- 更新 MySQL 中的数据。
- 删除 Redis 中对应的缓存。
为什么是删除(Invalidate)缓存,而不是更新缓存? 这是一个关键设计点!
- 性能:如果更新缓存,每次数据库写操作都要伴随一次缓存写操作,如果该数据并不经常被读取,那么这次缓存写入就是浪费资源的。
- 并发安全:在并发写场景下,更新缓存的顺序可能与更新数据库的顺序不一致,导致缓存中是旧数据。而删除操作是幂等的,更为安全。
Cache-Aside 如何保证一致性? 它通过'先更新数据库,再删除缓存'来尽力保证。但它依然存在不一致的窗口期:
- 线程 A 更新数据库。
- 线程 B 读取数据,发现缓存不存在,从数据库读取旧数据(因为 A 还没提交或刚提交)。
- 线程 B 将旧数据写入缓存。
- 线程 A 删除缓存。
这种情况发生的概率较低,因为通常数据库写操作(步骤 1)会比读操作(步骤 2)耗时更长(因为涉及锁、日志等),所以步骤 2 在步骤 1 之前完成的概率很小。但这是一种理论上的可能。
策略二:Write-Through / Read-Through Pattern(穿透读写模式)
在这种模式下,缓存层(或一个独立的服务)自己负责与数据库交互。对应用来说,它只与缓存交互。
- 写流程:应用写入缓存,缓存组件写入数据库。只有两个都成功后才会返回成功。



