大家好,我是程序员二叉。
简介
在Redis + MySQL主流架构中,缓存与数据库数据不一致是后端开发、面试高频重难点。很多线上故障、脏数据问题都源于双写流程设计不合理。
本文系统性梳理四种双写策略、两大主流执行顺序优劣、延时双删原理,以及金融级Canal订阅Binlog强一致性方案,全程标注重点、核心坑点,干货拉满,可直接收藏学习。欢迎点赞收藏关注。
一、为什么会出现双写不一致?
核心重点
数据库和缓存是两个独立存储组件,更新操作无法做到原子执行。
一旦两次操作顺序错乱、并发交织,就会出现:
- 数据库已更新,缓存还是旧数据
- 缓存更新完成,数据库未同步
最终产生脏数据,影响业务正常使用。
二、缓存+数据库 四种双写策略(全对比)
1. 先更新数据库 → 再更新缓存
强烈不推荐,线上禁用
执行流程:更新DB → 更新Redis
致命问题:
高并发场景下,后提交的数据库请求,反而先完成缓存更新,新数据被旧数据覆盖,永久数据不一致。
2. 先更新数据库 → 再删除缓存
业界主流通用方案(首选)
执行流程:更新DB → 删除Redis对应Key
核心思路:采用懒加载,查询时再回填最新数据。
优点
- 规避缓存互相覆盖问题,逻辑简单、性能高
- 冷数据自动淘汰,节省Redis内存
- 并发冲突概率极低,绝大多数业务场景完全够用
缺点(极端边界场景)
读+写并发交织会短暂出现脏数据:
- 读请求A:缓存未命中,查询MySQL拿到旧数据
- 写请求B:更新MySQL,并删除缓存
- 读请求A:把拿到的旧数据写入缓存
该场景发生条件苛刻,正常业务流量下几乎不会触发。
3. 先删除缓存 → 再更新数据库
不推荐,高并发必出问题
执行流程:删除Redis → 更新DB
优点
流程直观,新手易理解
致命缺点
高并发读写场景极易产生永久脏数据:
- 写请求A:删除缓存,开始更新数据库
- 读请求B:缓存为空,查询MySQL拿到旧数据,写入缓存
- 写请求A:数据库更新完成
最终结果:DB是新数据,缓存是旧数据,数据永久不一致。
4. 基于Binlog异步更新缓存
强一致性方案(金融/支付核心业务首选
执行流程:业务只操作DB → 中间件监听Binlog → 异步更新缓存
下文单独详解,适合零容忍脏数据场景。
三、延时双删策略(优化「先删缓存再更新DB」)
1. 适用场景
业务强制要求使用先删缓存流程,又要解决并发脏数据问题,采用延时双删做兜底。
2. 完整执行流程
- 第一次删除缓存
- 更新数据库
- 线程休眠一段时间(延迟等待)
- 第二次删除缓存
3. 原理详解
核心重点
休眠时间需要大于「查询数据库 + 写入缓存」的最大耗时。
目的:把并发读请求回填到缓存的旧数据再次清除,保证缓存为空,下次查询加载最新数据。
4. 优缺点
优点
有效解决「先删缓存」带来的并发脏数据问题,架构改动小。
缺点
- 线程休眠阻塞请求,接口性能下降
- 延迟时间依赖经验配置,难以精准适配所有网络环境
- 仅保证最终一致性,无法做到实时强一致
四、 强一致性终极方案:Canal 订阅 MySQL Binlog
1. 适用场景
金融、电商交易、支付、订单等不允许出现任何脏数据,对数据一致性要求极高的业务。
2. 核心原理
业务代码只操作数据库,完全不触碰缓存,依靠中间件实现缓存同步,做到解耦+强一致。
3. 整体架构&执行流程
- 业务请求执行
增/改/删,更新MySQL数据库 - MySQL 数据变更,自动记录Binlog 二进制日志
- Canal伪装成MySQL从节点,实时监听、拉取Binlog日志
- Canal 解析日志,提取表名、主键、变更后数据
- 将数据变更消息投递到RocketMQ/Kafka消息队列
- 消费服务监听MQ,消费消息并更新/删除Redis缓存
4. 核心优势
- 数据强一致:数据库为唯一数据源,缓存被动同步,从根源避免双写错乱
- 业务零侵入:原有代码无需改动,同步逻辑完全独立
- 高可用&削峰:依靠MQ异步解耦,不影响主业务吞吐量
- 支持重试、故障恢复,稳定性远高于代码硬编码双写
5. 缺点
架构复杂度提升,需要维护 Canal、MQ 等中间件,运维成本更高。
五、 方案选型总结(线上落地指南)
- 普通业务、允许短暂不一致
优先选择:先更新数据库,再删除缓存
实现简单、性能最优,互联网绝大多数项目通用。
- 历史架构固定,必须先删缓存
选择:延时双删策略
作为兜底优化方案,折中解决并发问题。
- 核心交易、金融、支付等强一致场景
选择:Canal + Binlog + MQ 异步同步
工业级强一致方案,零脏数据风险。
六、总结(面试速记 精简背诵版)
- 先更库再更缓存:并发覆盖,直接淘汰
- 先更库再删缓存:主流方案,冲突概率极低
- 先删缓存再更库:高并发必脏数据,慎用
- 延时双删:删缓存 → 更库 → 延时 → 再删缓存,兜底旧数据
- Canal订阅Binlog:强一致终极方案,监听数据库日志异步更新缓存