Mysql和Redis数据一致性总结
核心矛盾:MySQL 持久化强一致、Redis 高性能缓存,两者无法做到绝对实时强一致,只能根据业务选型:最终一致 / 准实时一致 / 强一致。
一、先说Redis缓存策略:旁路缓存模式(Cache-Aside)
流程
- 读:先查缓存 → 缓存命中直接返回 → 未命中查数据库 → 写入缓存
- 写:先更新数据库 → 再删除缓存(禁止更新缓存!)
也有其他缓存策略,但旁路缓存使用率最高。
旁路缓存(Cache-Aside)模型本身存在时序漏洞,属于架构固有问题,为了降低脏数据的危害,衍生出数据一致性方案。
二、主流 5 种数据一致性方案(从简单到高可用)
方案 1:先更库,后删缓存【标准版旁路缓存(Cache-Aside)】
流程
- 业务修改 / 新增 / 删除 MySQL 数据
- 数据库事务提交成功后
- 异步 / 同步删除 Redis 对应缓存 key
- 后续查询:缓存失效 → 查库 → 回写缓存
优点
- 实现简单、代码量少、性能高
- 绝大多数读多写少业务标配(商品、用户资料、配置)
存在问题
读写并发脏数据
线程 A 查缓存失效 → 查库 (旧数据) ,线程 B 更新库 + 删除缓存,线程 A 把旧数据写入缓存 → 脏数据会一直存在,直到缓存过期。
兜底解决
- 缓存统一加过期时间(天然兜底,脏数据自动过期)
- 非核心业务直接容忍,误差秒级
方案 2:延迟双删策略【降低并发脏数据・准实时一致】
针对方案 1 的并发漏洞,企业常用优化
流程
- 先删除缓存
- 更新 MySQL 数据库
- 业务休眠 / 延时(500ms~1s,根据业务 QPS 调整)
- 二次删除缓存
原理
第一次删:提前清空缓存;延时二次删:干掉并发请求写入的旧缓存数据。
优点
大幅降低并发导致的脏数据,一致性更强
缺点
- 同步延时会拖慢接口;
- 优化:改用MQ 异步延时删除,不阻塞主线程
本质仍是旁路缓存模型,只能大幅降低脏数据概率,延时删除清除了一些脏数据,如果线程A回填旧缓存时间晚于延时删除,仍有脏数据。
方案 3:MQ 异步删缓存【高并发・高性能・最终一致】
流程
- 业务更新 MySQL(本地事务保证落库)
- 发送「缓存删除消息」到 RocketMQ/Kafka/RabbitMQ
- 消费者消费消息,异步删除 Redis key
核心优势
- 解耦:数据库操作与缓存操作拆分
- 削峰:高写并发不阻塞接口,异步处理
- 可靠:MQ 持久化 + 重试,杜绝缓存漏删
- 完美支持延迟双删(延时消息队列)
适用场景
电商订单、营销活动、高并发写入业务
兜底
MQ 消费失败:死信队列 + 定时任务补偿
本质仍是旁路缓存模型,只能大幅降低脏数据概率,优化了延时双删的问题,如果线程A回填旧缓存时间晚于延时删除,仍有脏数据。
方案 4:Binlog 订阅同步【无侵入・强兜底】
核心组件
MySQL Binlog + Canal / Debezium + 消费服务 + Redis
流程
- MySQL 数据变更自动记录 Binlog
- Canal 监听 Binlog 日志,模拟从库拉取变更
- 解析 insert/update/delete 事件
- 自动删除 / 更新 Redis 缓存
优点
- 业务代码零侵入,不需要手动写删缓存代码
- 全局兜底,防止代码漏删、bug 导致缓存残留
- 适合老旧项目、多服务共用同一库场景
缺点
时效性稍弱(秒级延迟),不适合强实时业务
本质仍是旁路缓存模型,只能大幅降低脏数据概率,只是优化了方案,如果线程A回填旧缓存时间晚于删除,仍有脏数据。
方案 5:分布式锁 + 串行化【强一致・低并发】
针对金额、库存、账户等零容忍脏数据场景
流程
- 读写该数据前,先加分布式锁(Redisson)
- 同一 key 的读写请求串行执行
- 写:更库 → 删缓存 → 释放锁
- 读:查缓存 → 无则查库写缓存
优点
完全杜绝并发脏数据,最高一致性
缺点
并发能力下降,锁竞争影响性能,只适合低频核心数据
完全杜绝旁路缓存出现的并发脏数据,但是并发能力下降,锁竞争影响性能。
三、两种错误方案(禁止使用)
- 先删缓存,再更新数据库
脏数据出现概率远高于旁路缓存,高并发下必出脏数据,生产绝对禁用。
- 更新数据库 + 更新缓存
多写并发互相覆盖,缓存永久错乱,内存浪费严重。
四、三大特殊场景兜底方案
1. 缓存超时兜底
所有缓存 key必须设置过期时间(5 分钟~24 小时)哪怕代码异常、MQ 挂掉、Binlog 延迟,脏数据也会自动清理,是最终防线。
2. 定时任务全量刷新
低频次定时任务(凌晨 / 每小时),批量清理热点缓存、重置全量数据,修复长期不一致。
3. 读写分离场景
写主库、读从库:从库同步存在延迟,不要写完立刻读从库 + 写缓存;优化:核心数据读主库,或延长缓存过期时间。
五、生产环境最终选型
- 普通业务(用户 / 商品 / 资讯)
先更库 + 简单删缓存 + 缓存过期兜底
- 中高并发、要求数据较实时
先更库 + MQ 异步延迟双删
- 老旧系统、多服务共用库
业务删缓存 + Canal 监听 Binlog 二次兜底
- 资金 / 库存 / 交易核心数据
分布式锁 + 串行读写 + 弱化缓存(优先查库)
六、极简对比总结
| 方案 | 一致性 | 性能 | 复杂度 | 适用场景 |
|---|---|---|---|---|
| 先更库后删缓存 | 弱一致 | 极高 | 低 | 绝大多数业务 |
| 延迟双删 | 准实时一致 | 中 | 低 | 读写并发较高 |
| MQ 异步删缓存 | 最终一致 | 高 | 中 | 高并发写业务 |
| Binlog 同步 | 最终一致 | 高 | 高 | 无侵入兜底 |
| 分布式锁 | 强一致 | 低 | 高 | 核心金融数据 |
七、终极极简总结
- 除分布式锁外,所有缓存数据一致性方案,都无法彻底避免慢读请求延迟回填导致的脏数据;
- 延迟双删、MQ 延时、Binlog 兜底,核心作用是降低脏数据触发概率、缩短脏数据生命周期、补偿漏删场景;
- 旁路缓存(Cache-Aside)模型本身存在时序漏洞,属于架构天生的时序缺陷,无法通过单纯删缓存彻底解决;
- 技术选型本质是取舍:
- 要高性能 → 容忍最终一致、小概率脏数据
- 要强一致性 → 牺牲并发,加锁串行
八、组合方案
- 常规:先更新 DB、再删缓存
- 兜底 1:所有 key 必加过期时间(底线)
- 兜底 2:Canal 监听 Binlog 异步二次删缓存
- 特殊热点:热点 key 本地标记、永不过期、后台定时主动刷新
不加锁、不串行、高性能、脏数据概率极低
本作品采用《CC 协议》,转载必须注明作者和本文链接
关于 LearnKu
推荐文章: