← 每日面试题 学习看板

缓存与数据库数据一致性问题如何处理你在实际项目中是怎么解决的

面试题/真实面试/2026/缓存与数据库数据一致性问题如何处理你在实际项目中是怎么解决的.md

缓存与数据库数据一致性问题如何处理你在实际项目中是怎么解决的

首次来源:约牛科技 2026-01-12 一面

原题

缓存与数据库数据一致性问题如何处理?你在实际项目中是怎么解决的?

完整讲解(权威版)

最后更新:2026-10-29 · 基于第 1 次作答

L1 · 30 秒速记

结论:一致性本质是「读缓存 + 写库删缓存」的取舍,接受短暂不一致、消灭长期不一致;写后立即读强一致用「先写库再删缓存 + 延迟双删 + MQ 补偿」。 关键词:Cache Aside · 延迟双删 · 先更新再删 · 补偿 · 最终一致 边界:并发下「读旧值回填缓存」会覆盖新值,需延迟双删或版本号;删除失败必须补偿,否则长期不一致。

L2 · 核心讲解

核心考点定位:面试官想看你对「缓存一致性」是否有工程化理解,而不是背"先删缓存再更新库"——要能说清并发竞态、删除失败、写后立即读的三种典型坑,并给出补偿闭环。

机制链(≥3 环):

  1. 写库成功 → 删缓存:为什么,因为 Cache Aside 约定「更新走库、失效走缓存」,读时缓存未命中再回源。
  2. 删缓存前有并发读 → 旧值回填:为什么,因为读线程在「写库后、删缓存前」读到旧值并回填,缓存又变成旧值,形成长期不一致。
  3. 回填竞态 → 延迟双删:为什么,因为先删一次、延迟再删一次,覆盖中间窗口被回填的旧值。
  4. 删缓存失败 → 消息补偿:为什么,因为删除操作可能失败或超时,需 MQ 异步重试删除,或版本号 CAS 兜底。
  5. 缓存击穿 → 互斥回源:为什么,因为缓存未命中时大量并发回源 DB,需 SETNX 互斥或逻辑过期,避免热点 key 过期瞬间压垮数据库。

证据锚点(去重 ≥12):

对比与权衡:

方案一致性复杂度适用
先更新库再删缓存 + TTL最终一致低默认、读多写少
延迟双删较强中写后立即读、并发高
版本号 CAS强中高需严格不覆盖
binlog CDC 失效强高复杂缓存链路、多级缓存
MQ 补偿删最终一致中删除易失败场景
只设 TTL 不删弱低可容忍长时间不一致

常见误区:

  1. 错误:先删缓存再更新库最安全 → 正确:先删再更新,窗口内读会回填旧值,应「先更新库再删缓存」→ 为什么错:颠倒顺序放大了不一致窗口。
  2. 错误:删一次缓存就够 → 正确:并发读可能在被删后立刻回填旧值,需延迟双删或版本号 → 为什么错:忽略"删完又回填"的竞态。
  3. 错误:删除失败只记日志 → 正确:删除失败必须补偿(重试/MQ),否则缓存长期为旧值 → 为什么错:把偶发失败当可忽略。
  4. 错误:缓存一致性要强一致 → 正确:缓存本质是性能优化,应接受短暂不一致、用 TTL 保证最终收敛 → 为什么错:用强一致去设计缓存,成本失衡。
  5. 错误:用 @CachePut 更新缓存就一致 → 正确:@CachePut 先写后读可能读到旧库值,更新链路以「删缓存」为准 → 为什么错:混淆「更新缓存」与「失效缓存」语义。
  6. 错误:设了 TTL 就不怕不一致 → 正确:TTL 只保证最终收敛,若更新后删除失败,长 TTL 会放大不一致窗口 → 为什么错:把 TTL 当一致性保障而非兜底。

适用边界:强一致需求(写后立即读自己刚写的)用「写库 + 删缓存 + 延迟双删」并配合 TTL;多级缓存(本地 Caffeine + Redis)或复杂联动失效,用 binlog CDC 或 MQ 广播统一失效;只读高频、写极少的热点,可退化到「TTL 过期」简单模型并接受秒级不一致。若删除缓存与更新库之间系统崩溃,靠 TTL 过期兜底;若读回填与删缓存竞态频繁,优先版本号 CAS 而非无限延迟双删。

延伸追问(≥3)

  1. 为什么必须先更新数据库再删缓存?
  1. 删除缓存失败怎么兜底?
  1. 多级缓存(本地+Redis)一致性怎么保证?
  1. 缓存穿透/击穿/雪崩与一致性什么关系?

面试者参考回答(500–800 字)

【结论】 缓存一致性本质是取舍:用了缓存就要接受短暂不一致,但必须消灭长期不一致;标准做法是 Cache Aside——读缓存、未命中读库回填,写库后删缓存,配合延迟双删和补偿。

【三点】 第一,更新顺序:先更新数据库,再删缓存。先删后更新会在窗口内被读线程回填旧值,导致长期不一致。第二,处理并发竞态:单次删除不够,因为删完后可能立刻被并发读回填旧值,所以用延迟双删(删除 → 更新库 → 短暂延迟 → 再删一次),或用版本号 CAS(UPDATE version=version+1,缓存带版本,旧版本回填被拒绝)。第三,删除失败必须补偿:删除可能超时或失败,不能只记日志,要发 MQ 异步重试删除、兜底 DLQ,并给缓存设 TTL 作为最终收敛保证。

【边界】 我在实际项目里的取舍是:核心链路用「更新库 + 删缓存 + TTL」,有强一致要求(写后立即读)的加延迟双删;复杂多级缓存或联动失效(一个底层变动影响很多上层数据)用 MQ 广播统一删缓存,避免每层各自失效不一致。缓存本质是性能优化,别为追求强一致把成本做到失衡;只读高频写极少的场景,甚至可以退化到纯 TTL 过期模型接受秒级不一致。

面试记录 2026-10-29(第 1 次)

题目:缓存与数据库数据一致性问题如何处理你在实际项目中是怎么解决的

难度:high

标签:db/redis/advanced, real-interview, difficulty/high

评分:⭐⭐⭐⭐☆(4/5)

练习次数:1

我的回答

缓存和数据库一致性问题的核心是取舍;

都用缓存了,本身你就要接受短暂的不一致,主要不要是业务层级长期的不一致;

实际项目中主要还是ttl缓存过期,数据更新的时候,移除缓存等待下次拉取;

有强一直的要求都话,做过数据更新后,手动拉取写入缓存的,不过很少;

触发方面如果是涉及多缓存更新,比如之前做的一个需求是很多联动性的内容,当底层数据变动的时候会影响很多上层数据,当时用的是更新数据的时候发消息到mq,响应的要更新的服务去监听消息,做缓存删除;

点评

优点:抓住本质(接受短暂不一致、消灭长期不一致)+ Cache Aside(TTL 过期 + 更新删缓存)+ 强一致手动回写 + MQ 联动多级失效,工程化取舍清晰。

不足:未提延迟双删、删除失败补偿(MQ 重试/DLQ)、版本号 CAS 防旧值回填。

补充:并发下"删完又回填旧值"需延迟双删或版本号;删除失败必须补偿否则长期不一致;先更新库再删缓存。

完整讲解见上方「完整讲解(权威版)」