首页 > MySQL > 当前页面

如何保证缓存与数据库一致性?

2026-06-08 NEW个对象

📌 如何保证缓存和DB的一致性?

在高并发系统设计中,缓存(Cache)和数据库(DB)几乎是所有互联网系统绕不开的话题。 缓存负责提升访问性能,数据库负责数据持久化存储。 然而随着系统规模扩大,一个核心问题逐渐浮现:缓存与数据库如何保持一致?

1️⃣ 问题背景

在单体系统时代,业务通常直接访问数据库,虽然性能一般,但数据一致性天然得到保障。

随着用户量增长,大量请求直接打到数据库会导致:

  • 数据库CPU飙升
  • 磁盘IO成为瓶颈
  • 连接池耗尽
  • 响应时间持续增加

因此大多数系统都会引入Redis作为缓存层。

典型访问路径:

用户请求

Redis缓存
↓(未命中)
MySQL数据库

当数据发生修改时,如果缓存与数据库更新顺序不当,就会产生脏数据问题。

⚠️ 缓存和数据库本质属于两个独立系统,不存在天然事务,因此一致性问题永远存在。

2️⃣ 核心原理

缓存一致性的本质问题其实非常简单:

同一份数据同时存在两份副本:
  • 数据库中的数据
  • 缓存中的数据

只要数据被修改,就必须保证两个副本最终保持一致。

根据CAP理论和分布式系统特点,我们通常追求的是:

✅ 最终一致性(Eventual Consistency)

而非绝对强一致性。

因为缓存本身就是性能优化组件,如果为了绝对一致性而引入复杂分布式事务,反而得不偿失。

3️⃣ 数据结构分析

以商品信息为例:

商品表 Product

id = 1001
name = iPhone
price = 6999

Redis缓存结构:

Key:product:1001
Value:{
  "id":1001,
  "name":"iPhone",
  "price":6999
}

此时数据库和缓存保存的是同一份业务数据。

如果数据库修改为7999元,而缓存仍然保存6999元,那么用户读取到的数据就出现不一致。

4️⃣ 算法分析

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

❌ 不推荐

执行流程:

更新Redis

更新MySQL

如果Redis更新成功,而数据库更新失败,则缓存与数据库永久不一致。

因此这种方案几乎不会在生产环境使用。

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

执行流程:

更新MySQL

更新Redis

看起来合理,但依然存在问题。

如果数据库更新成功,而Redis更新失败,则缓存仍保存旧数据。

⚠️ 数据库成功,缓存失败,同样会出现脏数据。

方案三:更新数据库后删除缓存

✅ Cache Aside Pattern(旁路缓存模式)

这是互联网公司最常见方案。

执行流程:

更新MySQL

删除Redis缓存

下一次查询时:

查询Redis
↓(不存在)
查询MySQL

写入Redis

这就是经典的延迟加载机制。

5️⃣ 执行流程

下面分析一个高并发场景。

线程A:更新商品价格
线程B:查询商品价格

执行顺序如下:

线程A更新数据库

线程A删除缓存

线程B读取缓存失败

线程B查询数据库

线程B写入新缓存

最终缓存与数据库重新保持一致。

因此大部分互联网系统采用:

✅ 更新DB + 删除缓存

而不是更新缓存。

6️⃣ 实际案例

双写不一致问题

假设存在如下场景:

线程A更新DB

线程B读取缓存失败

线程B读取旧DB数据

线程A删除缓存

线程B写入旧缓存

最终旧数据再次进入缓存。

这就是经典缓存双写不一致问题。

延迟双删策略

解决方案:

delete(redis)
update(mysql)
sleep(500ms)
delete(redis)

第一次删除避免旧缓存存在。

第二次删除用于清理并发期间产生的脏缓存。

✅ 这种方案被称为延迟双删(Delayed Double Delete)

虽然不能保证100%一致,但能显著降低脏数据概率。

Binlog异步同步方案

大型互联网公司更倾向于采用Binlog驱动缓存更新。

执行流程:

业务更新MySQL

MySQL产生Binlog

Canal监听Binlog

MQ消息队列

删除Redis缓存

典型代表:阿里Canal架构。

数据库变成唯一数据源(Source Of Truth)。

所有缓存变更全部由Binlog驱动完成。

7️⃣ 优缺点分析

方案 优点 缺点
先更新缓存 速度快 极易不一致
更新DB更新缓存 逻辑简单 双写风险
更新DB删除缓存 实现简单 存在极端并发问题
延迟双删 一致性更高 实现复杂
Binlog+MQ 企业级方案 架构复杂

8️⃣ 面试常见问题

Q1:为什么删除缓存而不是更新缓存?

更新缓存需要知道缓存结构,同时存在更新失败问题。 删除缓存成本更低,后续查询自动重建缓存。


Q2:先删缓存还是先更新数据库?

标准答案是先更新数据库,再删除缓存。


Q3:延迟双删为什么有效?

能够清理并发读写期间重新写入的旧缓存。


Q4:缓存一致性能做到100%吗?

理论上可以,但成本极高。 实际系统通常追求最终一致性。


Q5:大型互联网公司如何做?

通常采用Canal + Binlog + MQ + Redis实现最终一致性。

9️⃣ 总结

🎯 缓存一致性的本质是解决数据库副本与缓存副本的数据同步问题。

🎯 最常见方案是 Cache Aside Pattern,即更新数据库后删除缓存。

🎯 在高并发场景下,可通过延迟双删降低脏数据概率。

🎯 企业级架构通常采用 Binlog + Canal + MQ 驱动缓存失效。

🎯 分布式系统中几乎不存在绝对强一致缓存,真正可落地的是最终一致性。

🚀 面试中如果被问到“如何保证缓存和数据库一致性”,最佳回答路径通常为: Cache Aside → 双写不一致 → 延迟双删 → Binlog异步同步 → 最终一致性。

相关文章

  • SQL语句

    表B:id,name,code,detail四个字段均有值;表A id,name,code,detail其中id,name有值并且与表B相对应,code,detail值为null。现在将B表中的code和detail值更新到A表中。

    NEW个对象 2024-12-25

  • SQL基础知识

    SQL基础知识

    NEW个对象 2025-01-13

  • 聚簇索引与非聚簇索引的区别

    聚簇索引:将数据存储与索引放到了一块,索引结构的叶子节点保存了行数据。 非聚簇索引:将数据与索引分开存储,索引结构的叶子节点指向了数据对应的位置。

    NEW个对象 2024-12-25

NEW个对象 NEW个对象
JAVA是世界上最好的语言