MySQL与Redis如何保证双写一致性详解

 更新时间:2025年10月29日 11:29:21   作者:程序员小假  
双写一致性是指在系统中同时使用缓存(如Redis)和数据库(如MySQL)时,如何确保两者的数据保持一致,这篇文章主要介绍了MySQL与Redis如何保证双写一致性的相关资料,需要的朋友可以参考下

前言

首先,我们必须明确一个核心观点:在分布式环境下,要实现强一致性(在任何时刻读取的数据都是最新的)是极其困难且代价高昂的,通常会严重牺牲性能。因此,在实践中,我们通常追求最终一致性,即允许在短暂的时间内数据不一致,但通过一些手段保证数据最终会保持一致。

下面我将从基础概念、各种策略、最佳实践到最新方案,为你详细讲解。

一、基础概念:为什么会有不一致?

在一个包含 MySQL(作为可靠数据源)和 Redis(作为缓存)的系统中,所有的写操作(增、删、改)都必须同时处理这两个地方。

这个过程中,任何一步失败或延迟都会导致不一致:

  1. 写 MySQL 成功,写 Redis 失败:导致 Redis 中是旧数据。
  2. 写 Redis 成功,写 MySQL 失败:导致 Redis 中是“脏数据”,数据库中不存在。
  3. 并发读写:一个线程在更新数据库,但还没更新缓存时,另一个线程读取了旧的缓存数据。

二、核心策略与模式

解决双写一致性有多种策略,我们需要根据业务场景(对一致性的要求、读写的比例等)进行选择。

策略一:Cache-Aside Pattern(旁路缓存模式)

这是最常用、最经典的缓存模式。核心原则是:应用程序直接与数据库和缓存交互,缓存不作为写入的必经之路。

  • 读流程
    1. 收到读请求。
    2. 首先查询 Redis,如果数据存在(缓存命中),直接返回。
    3. 如果 Redis 中没有数据(缓存未命中),则从 MySQL 中查询。
    4. 将从 MySQL 查询到的数据写入 Redis(以便后续读取),然后返回数据。
  • 写流程
    1. 收到写请求。
    2. 更新 MySQL 中的数据。
    3. 删除 Redis 中对应的缓存。

为什么是删除(Invalidate)缓存,而不是更新缓存?
这是一个关键设计点!

  • 性能:如果更新缓存,每次数据库写操作都要伴随一次缓存写操作,如果该数据并不经常被读取,那么这次缓存写入就是浪费资源的。
  • 并发安全:在并发写场景下,更新缓存的顺序可能与更新数据库的顺序不一致,导致缓存中是旧数据。而删除操作是幂等的,更为安全。

Cache-Aside 如何保证一致性?
它通过“先更新数据库,再删除缓存”来尽力保证。但它依然存在不一致的窗口期:

  1. 线程 A 更新数据库。
  2. 线程 B 读取数据,发现缓存不存在,从数据库读取旧数据(因为 A 还没提交或刚提交)。
  3. 线程 B 将旧数据写入缓存。
  4. 线程 A 删除缓存。

这种情况发生的概率较低,因为通常数据库写操作(步骤1)会比读操作(步骤2)耗时更长(因为涉及锁、日志等),所以步骤2在步骤1之前完成的概率很小。但这是一种理论上的可能。

策略二:Write-Through / Read-Through Pattern(穿透读写模式)

在这种模式下,缓存层(或一个独立的服务)自己负责与数据库交互。对应用来说,它只与缓存交互。

  • 写流程:应用写入缓存,缓存组件同步地写入数据库。只有两个都成功后才会返回成功。
  • 读流程:应用读取缓存,如果未命中,缓存组件自己从数据库加载并填充缓存,然后返回。

优点:逻辑对应用透明,一致性比 Cache-Aside 更好。
缺点:性能较差,因为每次写操作都必然涉及一次数据库写入。通常需要成熟的缓存中间件支持。

策略三:Write-Behind Pattern(异步写回模式)

Write-Through 的异步版本。应用写入缓存后立即返回,缓存组件在之后某个时间点(例如攒够一批数据或定时)批量异步地更新到数据库。

优点:写性能极高。
缺点:有数据丢失风险(缓存宕机),一致性最弱。适用于允许少量数据丢失的场景,如计数、点赞等。

三、保证最终一致性的进阶方案

为了弥补 Cache-Aside 模式中的缺陷,我们可以引入一些额外的机制。

方案一:延迟双删

针对 Cache-Aside 中提到的“先更新数据库,再删除缓存”可能带来的并发问题,可以引入一个延迟删除。

  1. 线程 A 更新数据库。
  2. 线程 A 删除缓存。
  3. 线程 A 休眠一个特定的时间(如 500ms - 1s)
  4. 线程 A 再次删除缓存。

第二次删除是为了清理掉在第1次删除后、其他线程可能写入的旧数据。这个休眠时间需要根据业务读写耗时来估算。

优点:简单有效,能很大程度上解决并发读写导致的不一致。
缺点:降低了写入吞吐量,休眠时间难以精确设定。

方案二:通过消息队列异步删除

为了解耦和重试,可以将删除缓存的操作作为消息发送到消息队列(如 RocketMQ, Kafka)。

  1. 更新数据库。
  2. 向消息队列发送一条删除缓存的消息。
  3. 消费者消费该消息,执行删除 Redis 的操作。如果删除失败,消息会重试。

这保证了删除缓存的操作至少会被执行一次,大大提高了可靠性。

方案三:通过数据库 Binlog 同步(最优解)

这是目前最成熟、对业务侵入性最小、一致性最好的方案。其核心是利用 MySQL 的二进制日志(Binlog)进行增量数据同步。

工作原理

  1. 业务系统正常写入 MySQL。
  2. 由一个中间件(如 Canal, Debezium)伪装成 MySQL 的从库,订阅 Binlog。
  3. 中间件解析 Binlog,获取数据的变更详情(增、删、改)。
  4. 中间件根据变更,调用 Redis 的 API 来更新或删除对应的缓存。

优点

  • 业务无侵入:业务代码只关心写数据库,完全不知道缓存的存在。
  • 高性能:数据库和缓存的同步是异步的,不影响主业务链路的性能。
  • 强保证:由于基于 Binlog,它能保证只要数据库变了,缓存最终一定会被同步。顺序也与数据库一致。

缺点

  • 架构复杂,需要维护额外的同步组件。
  • 同步有毫秒级到秒级的延迟。

四、总结与最佳实践选择

策略

一致性保证

性能

复杂度

适用场景

Cache-Aside + 删除

最终一致性(有微弱不一致风险)

绝大多数场景的首选,读多写少

Cache-Aside + 延迟双删

更好的最终一致性

对一致性要求稍高,且能接受一定延迟的写操作

Write-Through

强一致性

写多读少,且对一致性要求非常高的场景

Binlog 同步

最终一致性(推荐)

大型、高要求项目的最佳实践,对业务无侵入

通用建议

  1. 首选方案:对于大多数应用,从 Cache-Aside(先更新数据库,再删除缓存) 开始。它简单、有效,在大多数情况下已经足够。
  2. 进阶保障:如果 Cache-Aside 的不一致窗口无法接受,可以引入延迟双删消息队列异步删除来增强。
  3. 终极方案:当业务发展到一定规模,对一致性和系统解耦有更高要求时,投入资源搭建基于 Binlog 的异步同步方案。这是业界证明最可靠的方案。
  4. 设置合理的过期时间:无论如何,都给 Redis 中的缓存设置一个过期时间(TTL)。这是一个安全网,即使同步逻辑出现问题,旧数据也会自动失效,最终从数据库加载新数据,保证最终一致性。
  5. 业务容忍度:最重要的是,与产品经理确认业务对一致性的容忍度。很多时候,1-2秒内的数据不一致用户是感知不到的,不需要为此付出巨大的架构和性能代价。

总结

到此这篇关于MySQL与Redis如何保证双写一致性的文章就介绍到这了,更多相关MySQL与Redis保证双写一致性内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • MySQL执行过程中选择最佳的执行路径的方法(核心流程)

    MySQL执行过程中选择最佳的执行路径的方法(核心流程)

    文章介绍MySQL查询优化器如何通过解析、生成执行计划及成本评估选择最佳路径,依赖统计信息如表行数和索引基数,开发者需创建索引、更新统计信息、优化SQL写法及使用EXPLAIN工具辅助优化,感兴趣的朋友跟随小编一起看看吧
    2025-09-09
  • 一文带你了解MySQL之InnoDB统计数据是如何收集的

    一文带你了解MySQL之InnoDB统计数据是如何收集的

    通过show index可以看到关于索引的统计数据,那么这些统计数据是怎么来的呢,它们是以什么方式收集的呢,本章将聚焦于InnoDB存储引擎的统计数据收集策略,需要的朋友可以参考下
    2023-05-05
  • Windows7下安装使用MySQL8.0.16修改密码、连接Navicat问题

    Windows7下安装使用MySQL8.0.16修改密码、连接Navicat问题

    这篇文章主要介绍了Windows7下安装使用MySQL8.0.16修改密码、连接Navicat问题,本文给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
    2019-06-06
  • MySQL是如何保证数据的完整性

    MySQL是如何保证数据的完整性

    这篇文章主要介绍了MySQL是如何保证数据的完整性,帮助大家更好的理解和学习MySQL,感兴趣的朋友可以了解下
    2020-08-08
  • MySQL流程控制函数汇总分析讲解

    MySQL流程控制函数汇总分析讲解

    MySQL流程控制函数包括if、case、while、repeat、loop、leave、iterate等,可以在SQL语句中实现条件判断、循环、跳出等功能,提高了SQL语句的灵活性和功能性
    2023-04-04
  • MySQL在右表数据不唯一的情况下使用left join的方法

    MySQL在右表数据不唯一的情况下使用left join的方法

    这篇文章主要介绍了MySQL在右表数据不唯一的情况下使用left join的方法,针对右表符合条件表达式的记录数大于1条时left join所显示的结果需求来讲,需要的朋友可以参考下
    2016-03-03
  • 一文搞懂MySQL索引所有知识点

    一文搞懂MySQL索引所有知识点

    这篇文章主要带你搞懂MySQL索引的所有知识点,我们通常所说的索引,包括聚焦索引、覆盖索引、组合索引、前缀索引、唯一索引等,没有特别说明,默认都是使用B+树结构组织,感兴趣的小伙伴可以参考阅读
    2023-03-03
  • 解决当MySQL数据库遇到Syn Flooding问题

    解决当MySQL数据库遇到Syn Flooding问题

    Syn攻击常见于应用服务器,而数据库服务器在内网中,应该很难碰到类似的攻击,这篇文章主要介绍了当MySQL数据库遇到Syn Flooding问题 ,需要的朋友可以参考下
    2019-06-06
  • MySQL8 全文索引的实现方法

    MySQL8 全文索引的实现方法

    MySQL8支持全文索引和全文搜索,本文主要介绍了MySQL8全文索引的实现方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-08-08
  • MySQL非常重要的日志bin log详解

    MySQL非常重要的日志bin log详解

    bin log想必大家多多少少都有听过,它是MySQL中一个非常重要的日志,因为它涉及到数据库层面的主从复制、高可用等设计,所以本文就给大家详细的讲解MySQL非常重要的日志—bin log,需要的朋友可以参考下
    2023-07-07

最新评论