news 2026/7/22 4:01:23

搞懂 Redis 与数据库的数据一致性,看这一篇就够了

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
搞懂 Redis 与数据库的数据一致性,看这一篇就够了

在日常开发中,只要系统并发量稍微上来一点,我们都会不自觉地想到引入 Redis 来做缓存。这本来是件好事,读写速度直接起飞,数据库的压力也降下来了。

但是,引入缓存就像是一把双刃剑,它带来了一个让无数开发者头疼、也是面试官最爱问的终极问题:数据库和缓存的数据不一致怎么办?

今天,咱们就抛开那些晦涩难懂的学术名词,用大白话把这个“老生常谈”的问题扒得干干净净。


第一步:更新缓存,还是删除缓存?

当数据库里的数据发生变化时,我们要怎么处理 Redis 里的数据?拍脑袋一想,无非就两种操作:

  1. 更新缓存:数据库改了,我顺手把 Redis 里的数据也改了。

  2. 删除缓存:数据库改了,我直接把 Redis 里的这条数据删掉。等下次有人来查的时候,发现缓存没有,再去查数据库,然后把新数据放进 Redis。

结论先行:在绝大多数业务场景下,请直接选择“删除缓存”(这也是经典的 Cache Aside Pattern 旁路缓存模式的做法)。

为什么不推荐“更新缓存”?

  • 性能浪费:有些缓存数据不是直接从数据库原封不动拿出来的,而是经过了一系列复杂的计算。如果这条数据一天被修改了 100 次,但没人来查,你不仅白白计算了 100 次,还跟数据库交互了 100 次。

  • 并发安全问题:假设线程 A 和 B 同时在更新同一条数据。A 先更新数据库,B 后更新数据库;但在网络抖动的情况下,B 可能先更新了缓存,A 后更新了缓存。这时候,数据库里是 B 的新数据,缓存里却是 A 的老数据,直接脏数据了。

所以,“删就完事儿了”,简单粗暴且安全。等谁真正需要用到这条数据时,让他自己去查数据库并把结果写回缓存(也就是延迟加载的思想)。


第二步:先动数据库,还是先动缓存?

既然确定了策略是“删除缓存”,那随之而来的就是顺序问题。

方案一:先删除缓存,再更新数据库

这个方案听起来很合理,先把旧缓存干掉,再慢慢去更新数据库。但如果是高并发场景,极容易踩坑:

  1. 线程 A 请求更新数据,先把缓存删了。

  2. 还没等线程 A 去更新数据库,线程 B 跑来查询数据。

  3. 线程 B 发现缓存没了,就去查数据库,此时查到的是老数据

  4. 线程 B 把老数据又塞回了 Redis。

  5. 线程 A 终于把数据库更新完了。

结果:数据库是最新的,缓存里永远是老数据。凉凉。

方案二:先更新数据库,再删除缓存

这是目前业界公认的最佳实践。我们来看看这种顺序下的执行过程:

  1. 线程 A 更新了数据库。

  2. 线程 A 删除了缓存。

  3. 线程 B 来查询,发现缓存没有,去查数据库(此时拿到的是新数据),写入缓存。

完美!那这个方案有没有破绽呢?有,但概率极小。
只有在读写并发极其凑巧的瞬间:缓存刚好过期失效 -> 线程 A 查库拿到老数据 -> 线程 B 更新数据库 -> 线程 B 删除缓存 -> 线程 A 把老数据写回缓存。
这种情况发生的条件是:写数据库的操作比读数据库的操作还要快。但在实际中,写库(加锁、写日志等)通常比读库慢得多,所以这种极端情况极少发生。如果是为了应对这种极小概率事件,可以考虑给缓存加上过期时间(兜底方案)。


第三步:万一第二步失败了怎么办?

“先更新数据库,再删除缓存”看似完美,但不要忘了,现在的系统都是分布式的,网络随时会抖动。

如果数据库更新成功了,但是删除缓存失败了怎么办?缓存里依然是老数据,不一致的问题又出现了。

为了解决这个“失败”的问题,我们通常有以下两种工业级解法:

解法 1:消息队列重试机制(简单有效)

既然删缓存失败了,那我就多试几次直到成功为止。但如果用死循环去重试,会阻塞当前的业务线程。所以,我们可以引入消息队列(比如 RabbitMQ、Kafka、RocketMQ)。

  1. 更新数据库。

  2. 尝试删除缓存。

  3. 如果删除失败,把这个“删除 Redis 某某 key”的任务扔进消息队列。

  4. 自己写个消费者,监听这个队列,不断尝试删除缓存,直到成功。

优点:实现简单,能保证最终一致性。
缺点:业务代码里揉进去了消息队列的逻辑,对代码有侵入性。

解法 2:订阅 MySQL Binlog(优雅解耦)

如果你不想在业务代码里写一堆删除缓存、发消息的逻辑,可以使用阿里的开源组件Canal

Canal 的原理是把自己伪装成 MySQL 的从库(Slave)。当 MySQL 的数据发生变化时,会生成 Binlog(二进制日志)。Canal 监听到 Binlog 的变化后,再去触发删除 Redis 缓存的操作。

  1. 业务代码只管更新数据库,别的啥也不用管。

  2. Canal 监听 MySQL Binlog 发现某张表的数据变了。

  3. Canal 把变动信息丢给消息队列,或者直接自己起一段逻辑去删 Redis 里的 key。

  4. 如果删失败了,依然利用消息队列的机制进行重试。

优点:对业务代码零侵入,解耦得非常干净。
缺点:系统架构变复杂了,需要额外维护 Canal 组件。


补充彩蛋:面试最爱问的“延迟双删”

虽然刚才我们说了“先更新数据库,再删除缓存”是主流,但在一些历史遗留系统或者特殊场景下,你可能会听到“延迟双删”这个词。这其实是为了解决**“先删缓存,再更新数据库”**带来的并发问题而打的补丁。

流程大概是这样的:

  1. 先删除缓存。

  2. 更新数据库。

  3. 休眠一小会儿(比如 500ms)

  4. 再删除一次缓存

为什么要休眠?是为了等那些正在查老数据的读请求执行完,然后再补一刀,把他们可能写回 Redis 的老数据彻底删掉。
虽然这也是个办法,但在实际开发中,评估“休眠多长时间”纯粹是门玄学,且降低了吞吐量。所以现在的系统,除非万不得已,很少用这个方案了。知道这个概念,用来对付面试官就行。


最后总结一下

保证缓存和数据库一致性,记住以下三条核心准则:

  1. 兜底策略永远不能忘:给所有的 Redis 缓存都加上过期时间(TTL)。就算发生了一致性问题,等过期时间一到,缓存自动失效,数据自然就强行一致了。

  2. 日常业务首推先更新数据库,再删除缓存。这种方案能应对 99% 的场景。

  3. 高要求场景兜底:如果遇到删除缓存失败的情况,使用消息队列重试订阅 Binlog的方式,保证缓存最终一定会被删除。

说到底,分布式系统里没有绝对的“实时强一致性”,我们做的所有努力,都是在追求**“最终一致性”**。理解了这一点,你在处理缓存问题时就会游刃有余了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/14 14:13:47

mysql 回表、索引覆盖、索引下推的庖丁解牛

这三个概念常被误解为“晦涩的底层术语”或“只有 DBA 才需要关心的细节”。 但本质上,它们是MySQL 优化器在“减少磁盘 I/O"和“减少 CPU 计算”这两大核心目标上,进化出的三种生存智慧。 回表 (Table Lookup):是代价,是不得…

作者头像 李华
网站建设 2026/7/14 14:13:45

三相不平衡电网条件下PWM整流电路仿真模型与双闭环控制策略研究

三相不平衡电网条件下的三相PWM整流电路仿真模型。 抑制负序电流和直流电压,双闭环控制。电网电压不对称的时候玩PWM整流器,就像在颠簸路面开手动挡车——得同时踩离合控转速又要稳住方向盘。这次咱们用MATLAB搭个仿真模型,看看怎么用双闭环搞…

作者头像 李华
网站建设 2026/7/14 14:13:46

15 openclaw会话管理:处理用户状态与持久化数据

背景/痛点在构建高性能Web应用时,会话管理是绕不开的核心环节。许多开发者在实现会话功能时,常面临以下痛点:会话数据丢失:服务器重启或进程崩溃导致内存中的会话数据消失性能瓶颈:频繁的会话读写操作成为系统性能瓶颈…

作者头像 李华
网站建设 2026/7/14 14:13:47

智能降重与自然改写功能,6个AI论文网站助你高效完成学术写作

开头总结工具对比(技能4) �� 为帮助学生们快速选出最适合的AI论文工具,我从处理速度、降重效果和核心优势三个维度,对比了6款热门网站,数据基于实际使用案例: 工具名称 处理速度 降…

作者头像 李华