Redis 与 MySQL 数据一致性:我们到底能做到多“一致”?
2026 年了,很多系统依然在为“缓存和数据库不一致”头疼。
有人说:“缓存过期就行啦,脏数据最多存在几分钟,用户又不会死。” 有人却说:“我们金融场景,脏数据哪怕存在 500ms 都是事故。”
真相是:没有一种方案是绝对完美的,只有适合你业务容忍度的方案。
今天我们把目前主流的几种做法全部摆上台面,逐一拆解优缺点,并配上真实的业务案例,帮助你在下一次架构评审或双十一压测前,做出更清醒的选择。
一、先说结论:目前最推荐的组合(2025-2026 大厂主流)
| 读写比例 | 一致性要求 | 推荐方案组合 | 脏数据最坏窗口 | 复杂度 |
|---|---|---|---|---|
| 读 >> 写 | 允许几秒~几分钟 | Cache-Aside + 先更新库后删缓存 + 延时双删 | 缓存剩余 TTL | ★★☆☆☆ |
| 读 >> 写 | 希望 < 1~3秒 | Cache-Aside + 先更新库后删缓存 + MQ异步删 | 通常 < 1s | ★★★★☆ |
| 读 >> 写 | 极致最终一致 | Cache-Aside + Canal + MQ | 毫秒~秒级 | ★★★★★ |
| 写 ≈ 读 | 允许丢失少量 | 先写 Redis → 异步批量回写 MySQL | 可丢失几秒数据 | ★★★★☆ |
| 写 > 读 | 必须强一致 | 只用 MySQL + 分布式锁(基本放弃 Redis) | 无脏数据 | ★★★★★★ |
绝大多数 ToC 业务(商品、内容、推荐、用户资料等)最终会落在第 2 或第 3 行。
下面我们逐个拆解,并通过案例说明这些组合的实际运用。
二、方案详解 + 真实案例
1.方案 1 – Cache Aside(旁路缓存) + “先更新库,后删缓存”
(1)基本流程
写流程:
1. 更新 MySQL
2. 删除 Redis(不管成功与否)
读流程:
1. 先查 Redis,命中 → 返回
2. 未命中 → 查 MySQL → 回写 Redis → 返回
(2)最坏情况举例(经典脏数据案例)
场景:商品详情页,缓存 TTL = 10 分钟
时间线:
- T0:管理员把价格从 99 → 199(更新 MySQL 成功)
- T0+100ms:删缓存失败(网络抖动、Redis 慢)
- T0+200ms:用户 A 访问详情页 → Redis 还是旧值 99 → 回写 Redis(更糟糕,把 99 又写回去了)
- T0+10min:缓存过期,用户下次访问才看到 199
脏数据持续时间:最坏 10 分钟
(3)改进版 – 延时双删!!!
更新 MySQL → 删除 Redis → sleep(500~1000ms) → 再次删除 Redis
注意:
为什么 sleep 有效?
因为在“先更新库 → 删缓存”之后,如果有读请求在“更新完成但第一次删还没成功”这个极短窗口读到了旧值并回写了 Redis,那么第二次删就能把这个脏值删掉。
真实案例:
某中型电商 2023~2025 年一直在用延时双删 + 1秒 sleep,脏数据投诉率从每月几十条降到每月 1~2 条,可接受。
2.方案 2 – MQ 异步删缓存(目前性价比最高的生产方案)
(1)流程
1. 更新 MySQL(事务内)
2. 事务提交成功后 → 发 MQ 消息(payload 包含 key 或 key 前缀)
3. MQ 消费者收到消息 → 删除对应 Redis key
└─ 失败 → 重试(指数退避)或进死信队列
(2)优点
- 删缓存失败不影响主流程
- MQ 持久化 + 多消费者 → 高可用
- 可批量删(前缀删除)
(3)真实案例:某内容平台(日活千万级)
- 文章审核通过后修改标题、正文
- 发 MQ 消息 “article:update:{id}”
- 消费者收到后删 article:{id} 和 article:feed:tag:{tagId} 等相关 key
- 即使审核服务重启、网络分区,MQ 重试机制保证最终删除
- 脏数据窗口通常在 200ms~2s 内(取决于 MQ 积压情况)
3.方案 3 – Binlog 监听(Canal / Debezium) + MQ
MySQL → binlog → Canal/Debezium → 解析变更 → 发 MQ(或直接推 Redis)
→ 消费者根据变更类型删/更新对应缓存
(1)优点:
- 完全解耦,业务代码几乎零侵入
- 即使服务重启、Redis 短暂不可用,最终也能对齐
- 支持复杂场景(比如一张表变更影响多个 key)
(2)真实案例:某直播平台礼物背包、用户资产
- 用户送礼 → 扣减背包、加主播收入
- Canal 监听到 users、gift_bag、broadcaster_income 三张表变更
- 统一推到 MQ topic “cache-invalidate”
- 消费者根据表名 + 主键 → 删除对应 Redis key(支持前缀、Hash 字段级删除)
脏数据窗口通常在 100ms~800ms,非常稳定。
4.方案 4 – 先写 Redis,后异步写 MySQL(计数器/点赞/浏览量专用)
(1)流程
点赞功能:
1. Redis INCR like_count:{postId}
2. 返回成功给用户
3. 异步任务(每 3~10 秒)批量 flush 到 MySQL
(2)真实案例:短视频平台日增赞 10 亿+
- 几乎所有点赞操作只走 Redis
- 每 5 秒一个定时任务 select + update + delete 批量同步
- 极端宕机丢失 5 秒数据 → 运营可接受(会补赞活动)
- 读性能极致,写性能极致
三、最后一次总结表格(建议收藏)
| 你最在意的点 | 推荐方案 | 预计脏数据时长 | 引入组件 | 团队维护成本 |
|---|---|---|---|---|
| 简单、成本最低 | 延时双删 | 几秒~TTL | 无 | 极低 |
| 中等并发,要稳定 | 先更新库 + MQ 异步删 | < 3s | MQ | 中 |
| 高并发、大流量 | Canal + MQ | < 1s | Canal + MQ | 高 |
| 纯计数器、允许少量丢失 | 先写 Redis → 异步回写 MySQL | 可丢失几秒 | 定时任务 | 中 |
| 金融、对账、绝对不能脏 | 直接走 MySQL + 分布式锁(慎用 Redis) | 无 | 分布式锁 | 高 |
选方案的时候,先问自己三个问题:
- 脏数据最多能忍几秒?(用户能感知吗?)
- 写 QPS 峰值大概多少?(会不会把 MQ 打爆?)
- 团队愿意为一致性多维护几套中间件吗?
欢迎留言区讨论:
你们现在用的是哪种方案?
遇到的最严重的一次不一致事故是什么样的?
希望这篇对你有帮助~记得点赞收藏。
更多推荐




所有评论(0)