缓存一致性问题为什么总在删缓存、改数据库之后出事?
缓存一致性难处理,不是因为大家没听过删缓存,而是事务提交时机、删除失败、并发回填旧值和回源压力会在同一次写操作后一起冒出来。真正麻烦的地方,通常出在这些细节刚好撞在同一段时间窗里。
缓存一致性这个话题,很多团队第一次认真面对,通常都不是在设计评审会上,而是在事故群里。
常见场景都很像:
- 某个商品、配置、用户信息刚改完,接口却还在返回旧值
- 数据库明明已经更新成功,缓存里还是老数据
- 大家说“不是已经删缓存了吗”,结果删完以后数据库反而被打高了
- 写接口一慢,读流量又开始回源,最后慢 SQL、连接池、线程池、接口 RT 一起抖
于是讨论很快会滑向几个熟悉的问题:
- 到底该先删缓存,还是先写数据库
- 要不要双写
- 延迟双删是不是银弹
- 删缓存失败到底算不算大问题
- 为什么只是一个缓存更新动作,最后总能演变成数据库和接口事故
这篇文章不打算再把缓存一致性写成“更新数据库 + 删除缓存”八字口诀,而是想把它还原成真实工程问题:
缓存一致性出事,往往不是因为大家不知道要删缓存,而是因为并发读写、事务提交时机、删除失败、回源放大和业务容忍度这几件事,刚好在同一条链路里叠在了一起。
如果你只想先记一句话,可以先记这个默认判断:
对大多数“数据库是事实来源”的业务,默认优先考虑 先更新数据库,再在事务提交后删除缓存;但这只是起点,不是终点。真正决定会不会出事的,是删除动作能不能补偿、并发读会不会把旧值写回、以及数据库能不能扛住删除后的回源流量。
删缓存和改数据库总是一起出事时
| 你现在看到的现象 | 建议先看哪条线 | 为什么 |
|---|---|---|
| 删缓存、改数据库之后出现旧值、脏读或回源放大 | 从本文开始拆 | 这篇先把时序、补偿和回源放大放到一起看 |
| 你已经在比较双写、延迟双删和 binlog 方案 | 先看方案对比那篇文章 | 那篇更方便直接做方案取舍 |
| 你还没确认是缓存类型问题还是一致性问题 | 先看穿透 / 击穿 / 雪崩或回源放大相关文章 | 先分场景 |
| 你想讨论热点 key、本地缓存或分片热点 | 先看热点治理相关文章 | 不要把热点问题硬套成一致性问题 |
先把一致性问题落到具体风险上
如果你已经遇到删缓存后旧值回写、回源放大或事务时序问题,就直接按本文往下收窄。
这里最值得先看清的,不是术语本身,而是几件会一起把现场搞坏的事实:事务什么时候真正提交、删缓存有没有失败或延迟、并发读会不会把旧值重新写回、热点 key 删除后数据库能不能扛住回源。
先把事故时间线对齐
如果你现在就在删缓存 / 写库之后的事故现场,第一轮别急着讨论双写还是延迟双删,先把事务提交时间、删缓存发生时间、删除失败或重试记录、回源峰值时间和业务能容忍的旧值窗口对齐。很多团队事后都会把问题归结成“顺序错了”,但现场真正把系统拖偏的,往往还是时序撞车、补偿缺位和并发回填旧值。
- 先定业务能容忍多长时间的旧值,不要跳过容忍度直接谈方案。
- 再确认删除缓存动作到底发生在事务提交前、提交后,还是失败后无人补偿。
- 再看读路径有没有把旧值重新写回缓存。
- 如果热点 key 删除后明显回源,记得把数据库、连接池和接口慢一起拉回来。
- 最后再决定是修时序、补补偿,还是继续看具体一致性方案。
一、缓存一致性最容易被说轻的一点:它不是“顺序题”,而是“时序题”
很多人第一次听缓存一致性,脑子里想的都是一个静态问题:
- 写库
- 删缓存
- 结束
但线上真正麻烦的地方在于,系统里从来不只有一个线程、一个请求、一个时间点。
真正的问题往往是这些动作在并发下同时发生:
- A 请求正在更新数据库
- B 请求此时正好来读缓存
- C 请求又把旧值查出来回填到缓存
- D 请求因为下游慢导致事务迟迟没提交
- 删除缓存的消息刚好丢了,或者重试晚了几秒
所以缓存一致性不是简单的“先做 A 还是先做 B”,而是:
- 数据库什么时候真正提交成功
- 删除缓存发生在提交前还是提交后
- 并发读请求会不会在空窗期把旧值重新写回
- 删除失败后有没有重试、补偿或异步兜底
- 业务能不能接受几百毫秒到几秒的旧数据
也就是说,一致性问题的本质不是动作本身,而是动作之间的时间关系。
这也是为什么很多方案在 PPT 上看起来都说得通,线上一遇到并发和失败场景就开始出问题。
二、先别急着讨论方案:先分清你到底想要什么级别的一致性
缓存一致性之所以老是越讨论越乱,还有一个根本原因:很多团队讨论的是“方案”,但没有先说清楚“业务容忍度”。
这一步非常重要,因为不是所有数据都值得用同一种一致性成本去处理。
1. 可以容忍短时间旧值的场景
例如:
- 用户昵称、头像、资料页展示信息
- 活动文案、首页配置、推荐标签
- 非关键统计数字
这类数据通常可以接受:
- 缓存比数据库晚几百毫秒到几秒更新
- 极少量请求读到旧值
- 异步补偿后最终收敛
这时更适合考虑“数据库优先 + 删除缓存 + 重试补偿”这种工程上更稳的方案。
2. 能容忍最终一致,但不能接受长时间脏数据的场景
例如:
- 商品价格
- 订单状态展示
- 营销资格结果
- 用户权限开关
这类数据通常要求:
- 旧值窗口尽量短
- 删除失败不能悄悄丢掉
- 最好有异步重试、消息补偿或 binlog 驱动的兜底链路
3. 几乎不能容忍脏读的场景
例如:
- 库存强约束
- 支付状态强校验
- 风控与额度判断
- 核心交易决策字段
这类场景最重要的结论往往不是“选更复杂的缓存方案”,而是:
- 不要把这类字段直接建立在普通读缓存之上
- 关键校验回数据库或强一致存储
- 用版本号、CAS、串行化更新或业务降级保护,而不是幻想“缓存也能顺便绝对一致”
所以讨论缓存一致性前,最该先问的一句不是“要不要延迟双删”,而是:
这个字段如果短时间读到旧值,业务到底能不能接受?
这句话,会直接决定后面方案的复杂度上限。
三、为什么“先删缓存,再写数据库”最容易在并发读写下出事
很多人第一次接触缓存一致性,会觉得“先删缓存,再写数据库”很自然:先把旧缓存删掉,后面再把数据库改掉,不就好了吗?
但它恰恰是最容易被并发读打穿的一种顺序。
一个很典型的时间线
假设商品价格原来是 100,现在要改成 120。
- 写请求 A 先删除缓存
- A 还没来得及更新数据库
- 读请求 B 发现缓存没了,开始回源数据库
- 这时数据库里还是旧值 100
- B 把 100 重新写回缓存
- A 再把数据库改成 120
- 最终结果变成:数据库是新值,缓存却又回到了旧值
这就是最经典的“读请求把旧值回填回缓存”问题。
它危险的地方在于:
- 你明明删过缓存
- 数据库最终也更新成功了
- 但因为删除动作发生得太早,读流量在空窗期把旧数据重新种回去了
所以先删缓存,再写数据库的问题不在“删错了”,而在“删得太早”。
这类方案为什么现实里还会被用到
因为它实现简单,而且在低并发、低风险业务里有时“看起来也没出事”。
但一旦满足下面几个条件,风险就会显著升高:
- 这个 key 读流量大
- 更新事务比较慢
- 数据库提交前还有远程调用、批量处理、锁等待
- 缓存 miss 后会立即回源并回填
一旦这些条件叠加,先删后写就是高危路径。
四、“先写数据库,再删缓存”为什么是默认更稳的起点,但依然不够
这也是业界最常见、最推荐的默认做法。
它比“先删再写”更稳,原因很直接:
- 先让数据库成为最新事实来源
- 再把缓存删掉
- 后续读请求即使回源,也更有机会拿到新值
这确实把最危险的时序窗口缩小了很多,但它不是银弹,因为它还会遇到另外几类问题。
1. 数据库改成功了,但删缓存失败了
这是最典型、也最容易被低估的问题。
如果写库成功、删缓存失败,结果就是:
- 数据库已经是新值
- 缓存还保留旧值
- 后续读请求持续命中旧缓存
- 直到 TTL 自然过期或者人工修复前,脏数据都可能一直存在
这类问题为什么线上常常比想象中严重?
因为很多系统对“删缓存失败”没有足够重视:
- 没有重试
- 没有告警
- 没有补偿日志
- 没有消息兜底
结果删除失败在系统里只是一次普通异常,业务却在读旧数据。
2. 你删缓存的时机,可能早于事务真正提交
这也是特别容易踩的坑。
很多代码看起来是这样:
- 开启事务
- 更新数据库
- 立即删除缓存
- 事务方法结束后才真正提交
如果缓存删除发生在事务提交前,问题就来了:
- 并发读请求此时 miss 了缓存
- 它去数据库读时,可能还读不到新提交的数据
- 又把旧值重新写回缓存
所以真正稳的关键不是“写库后删缓存”,而是:
数据库事务真正提交成功之后,再删除缓存。
这也是为什么很多项目最终会落到:
- 事务提交后回调删缓存
- 事务提交后发消息异步删缓存
- binlog / CDC 驱动缓存失效
因为删除动作必须站在“新数据已经成为事实”的时间点之后。
3. 删除成功了,也可能引发回源放大
这点常常被忽略。
缓存删掉之后,如果这个 key 很热,接下来会发生什么?
- 大量读请求一起 miss
- 短时间内都去打数据库
- 数据库、连接池、线程池压力一起升高
- 如果数据库本来就有慢 SQL、长事务、锁等待,接口 RT 会继续放大
也就是说,先写库再删缓存解决的是“旧值问题”,但不自动解决“回源压力问题”。
这就是为什么缓存一致性文章,最后总会和数据库、接口慢、热 key 连在一起。
五、为什么“更新数据库 + 更新缓存”的双写,看起来彻底,实际上更容易把两个真相源写崩
很多人会本能地觉得:
- 既然删缓存还有空窗期
- 那不如写完数据库以后,直接把缓存也更新掉
看上去好像更“实时”,但这其实把问题从“缓存失效”换成了“双写顺序和失败处理”。
双写最大的问题:你开始维护两个真相源
一旦你选择同时写数据库和缓存,就必须回答这些问题:
- 先写数据库还是先写缓存
- 一个成功一个失败怎么办
- 重试时会不会乱序
- 多个并发写入时,后写的旧值会不会覆盖先写的新值
- 缓存更新是不是幂等
- 回滚时怎么处理
举个典型例子:
- 请求 A 把价格改成 120
- 请求 B 紧接着把价格改成 130
- A 写数据库成功,也开始更新缓存
- B 也写数据库成功,并更新缓存为 130
- A 的缓存更新因为网络抖动晚到一步,又把缓存写回 120
最终结果就是:
- 数据库是 130
- 缓存却变成了 120
这类问题比“删除失败”更难排,因为它不是没有动作,而是动作顺序乱了。
双写什么时候才值得考虑
不是完全不能做,但前提通常非常苛刻:
- 你能保证写入顺序
- 你有版本号或时间戳防覆盖
- 缓存写入具备幂等和条件更新能力
- 业务强烈需要尽量缩短旧值窗口
否则对大多数普通业务来说,双写的复杂度往往高于收益。
所以工程上更常见、也更稳的默认思路仍然是:
- 数据库做唯一事实来源
- 缓存只做派生副本
- 写时优先让缓存失效,而不是把缓存提升成第二事实来源
六、延迟双删为什么经常被拿出来救场,但它更像补丁,不是银弹
延迟双删是这个话题里最容易被神化的方案之一。
它的典型思路是:
- 先删缓存
- 再更新数据库
- 休眠一小段时间
- 再删一次缓存
它想解决的问题很明确:
- 第一次删除后,可能有并发读把旧值写回去了
- 那就隔一小段时间再补删一次
这套思路在一些低风险、能容忍短暂旧值的业务里,确实能降低脏缓存残留概率。但要注意几个事实。
1. 它解决的是“概率”,不是“确定性”
只要你的读写并发窗口比预估更长,或者事务提交时间、下游调用时间有抖动,第二次删除也可能删早了或者删晚了。
所以延迟双删本质上是:
- 用时间延迟去覆盖一部分竞争窗口
- 而不是从机制上消灭并发问题
2. 延迟多久,本身就没有万能答案
如果延迟太短:
- 并发读可能还没完成回填
- 第二次删除没有起到效果
如果延迟太长:
- 脏数据窗口又变大
- 系统行为更难预测
- 重试任务、消息积压也可能进一步复杂化
3. 它会让系统出现更多异步状态
一旦上了延迟双删,你还要考虑:
- 第二次删除由谁触发
- 失败了怎么补偿
- 重试是否幂等
- 多次更新如何避免乱序删错
4. 它更适合哪些场景
如果满足这些条件,延迟双删才相对值得考虑:
- 业务可以容忍很短时间旧值
- 你已经明确知道问题是并发读回填旧值
- 数据更新频率不算极高
- 第二次删除链路有重试和告警
但如果你的业务要求高一致,或者读写链路很复杂、事务时间长、下游波动大,那延迟双删通常只是在补锅,不是在治根。
七、真正更稳的工程默认解:更新数据库后,在事务提交后删除缓存,再加上异步补偿
对大多数“数据库是事实来源”的业务,如果没有特别强的强一致要求,一个更稳的默认方案通常是三件事配套考虑。
1. 写路径:事务提交后删除缓存
重点不是“写库后删缓存”,而是:
- 只有当数据库事务真正提交成功
- 才触发缓存删除
这样可以尽量避免“删缓存发生在提交前,读请求又把旧值写回”的问题。
2. 删除失败要有补偿,而不是只打一条日志
删除失败一定要被当成真正的业务风险,而不是普通技术噪音。
常见补偿方式包括:
- 同步删除失败后本地重试
- 通过消息队列异步重试删除
- 记录待补偿任务,后台扫描补删
- 基于 binlog / CDC 监听数据库更新事件再删缓存
这里真正重要的不是“用了哪个中间件”,而是:
- 失败要可见
- 重试要可追踪
- 删除要幂等
- 补偿链路不能悄悄失效
3. 对高热点 key,再补回源保护
如果更新的是高热点 key,仅仅删除缓存还不够。
还要进一步考虑:
- 是否要做互斥回源
- 是否需要热点 key 逻辑过期或异步刷新
- 是否要对回源查询做限流、隔离、降级
- 数据库是否能扛住瞬时 miss 风暴
这一层如果不补,缓存一致性虽然“逻辑上没错”,系统还是可能因为回源流量而出事故。
这也是为什么这篇文章一定要和热 key、缓存故障分类、数据库慢链路连起来看。
八、为什么删缓存之后,问题总会一路串到 MySQL、长事务和接口慢
很多团队第一次觉得“缓存一致性好像很麻烦”,不是因为读到了旧值,而是因为一删缓存,系统别的地方开始一起抖。
根因通常是这条链:
- 写请求更新数据库并删除缓存
- 热点读流量大量 miss
- 请求回源数据库
- 数据库原本就有慢 SQL、锁等待或长事务
- 回源查询耗时继续拉长
- 连接池连接被占得更久
- 应用线程池和请求线程一起开始堆积
- 最终表现成接口整体变慢
这条链里,缓存一致性只是起点,不是终点。
1. 如果回源查询本身就慢
那问题会非常直接地传到数据库。
下一步直接看:
因为很多缓存删除后的事故,本质上是“缓存挡板突然没了,把原本数据库里的坏路径全部暴露出来”。
2. 如果数据库事务本来就很长
那么缓存删除后的回源流量,可能又会撞上还没提交完的事务、锁等待和热点更新。
下一步直接看:
因为长事务会把“更新成功但读不到新值”的窗口拉得更长,也会把连接和锁一起拿住。
3. 如果接口已经开始整体变慢
那就说明问题已经不是缓存层局部问题,而是整条调用链在变形。
下一步直接看:
因为你需要把缓存 miss、数据库回源、连接池、线程池和下游调用一起串起来看。
九、读写并发下最容易被忽略的,不是“删不删”,而是“谁会把旧值再写回去”
缓存一致性里真正最危险的角色,往往不是写请求,而是普通读请求。
因为很多系统的读路径默认都会这么做:
- 先查缓存
- miss 了就回数据库
- 再把查到的结果写回缓存
这个模式平时很好用,但在写入并发、事务提交延迟、删除失败、回源波动下,它也会变成旧值回填机器。
所以排查缓存一致性时,一定要把读路径也拿出来看清楚:
- 缓存 miss 后是不是立即回源
- 回源查到旧值后会不会无条件回填
- 回填是否带版本号、时间戳或最基本的幂等保护
- 热点 key miss 时会不会有很多请求一起回源
很多“删了缓存还是脏”的现场,最后问题不在删除动作本身,而在于:
- 读路径太积极
- 回填逻辑太简单
- 并发 miss 完全没有保护
十、缓存一致性不是纯缓存问题,热 key 会把它从旧值问题放大成容量问题
如果一个 key 又热、又经常更新,那缓存一致性就会比普通业务难很多。
因为每次写入都意味着:
- 这个热点缓存可能被删掉
- 下一波大量请求开始回源
- 旧值窗口、回源压力和接口 RT 同时波动
这时你讨论的就不再只是“数据有没有及时更新”,而是:
- 热点 key 是否值得删缓存
- 能不能改成逻辑过期 + 后台刷新
- 能不能把高写频字段和高读频字段拆开
- 是否要让部分读请求接受短时间旧值,换系统稳定性
这也是为什么缓存一致性和热 key 经常必须一起看。
如果你已经确定问题集中在少数热点 key,而不是大面积缓存失效,下一篇直接看:
因为很多人以为自己在解决一致性,实际上遇到的是“热 key 更新导致回源容量打爆”。
十一、一个更接近线上真实的案例:为什么“只是改个价格并删缓存”,最后会把接口 RT 一起拖上去
假设有个商品详情接口,平时大量请求都命中 Redis,商品价格更新频率不高,但一到活动期会集中改价。
写链路原本是这样:
- 开启事务
- 更新商品表价格
- 同步删 Redis 商品详情缓存
- 提交事务
看起来没什么大问题,但活动期开始后出现了下面的现象:
- 改价任务一跑,详情接口 RT 就开始抖
- 数据库查询量明显升高
- 连接池活跃连接数长期高位
- 个别时段读到了旧价格
往下看时间线,问题就清楚了:
第一步:删除缓存发生在事务真正提交前
也就是说,虽然代码顺序是“更新数据库 -> 删除缓存”,但数据库事务还没真正提交。
第二步:热点读请求开始大量 miss
商品详情是热点 key,一删缓存,大量请求开始回源数据库。
第三步:部分回源读到了旧值,又写回缓存
因为事务还没提交完成,数据库里部分时刻还读到旧价格,于是旧值被回填回缓存。
第四步:回源查询本身又把数据库和连接池打高
活动期写流量和读流量都高,数据库里又正好有一条商品详情查询走得不够稳,结果:
- 回源查询变慢
- 连接持有时间变长
- 请求线程等待时间增加
- 接口 RT 一起抖动
最终结论
这次问题表面上是“缓存一致性”,实际上是下面几件事叠加:
- 删除时机早于事务提交
- 热点 key 回源没有保护
- 数据库回源链路本身不够稳
- 读路径会把旧值重新回填
这就是为什么线上一致性问题很少只修一个点就结束。你得把整条链修顺。
十二、遇到缓存一致性问题时,一条更稳的排查顺序是什么
如果以后再遇到“删缓存、改数据库之后总出事”的问题,我更建议按这条顺序走。
第 1 步:先定业务容忍度
先回答:
- 这个字段能不能短时间读到旧值
- 旧值窗口能接受多长
- 允许最终一致,还是必须强一致
不先回答这个问题,后面的复杂度都没边界。
第 2 步:再看写链路时序
重点确认:
- 是先删缓存还是先写数据库
- 数据库事务什么时候真正提交
- 删除缓存发生在提交前还是提交后
- 删除失败有没有重试和补偿
第 3 步:再看读链路回填逻辑
重点确认:
- miss 后是否立即回源
- 回源结果是否无条件回填
- 并发 miss 是否有限流、互斥或合并请求
- 是否可能把旧值重新种回缓存
第 4 步:再看热点与回源压力
重点确认:
- 这个 key 热不热
- 删除后会不会形成热点回源
- 数据库是否能扛住 miss 风暴
- 是否已经表现成热 key 或击穿问题
第 5 步:最后再看数据库和接口链路有没有被放大
重点确认:
- 回源 SQL 是否稳定
- 是否有长事务、锁等待
- 连接池、线程池、接口 RT 是否同步恶化
- 是否已经从一致性问题变成容量问题
这条顺序的好处是:
- 不会一上来就陷入“选哪个术语”
- 能先把一致性问题缩到具体的时序和链路位置上
- 也能更早发现问题是不是已经溢出到数据库和接口层
十三、最容易出现的几个误判
误判 1:只要删缓存就够了,删除失败问题不大
错。
删除失败如果没有重试和补偿,缓存可能会持续保留旧值,直到 TTL 过期。这不是小概率边角问题,而是标准脏数据来源。
误判 2:代码里“先 update 再 delete”,就等于事务提交后删缓存
错。
很多框架下,事务真正提交发生在方法返回或拦截器阶段。代码顺序和提交顺序不是一回事。
误判 3:延迟双删能解决所有一致性问题
错。
延迟双删只能覆盖一部分并发窗口,解决不了删除失败、乱序、长事务和热点回源放大。
误判 4:双写缓存和数据库更实时,所以一定更好
错。
双写把问题从“删除失败”换成了“两个真相源的顺序和覆盖问题”,复杂度通常更高。
误判 5:一致性问题只是 Redis 的事,和数据库性能没关系
错。
一旦删缓存触发大规模回源,慢 SQL、长事务、连接池、线程池、接口 RT 都会被一起带出来。
十四、FAQ:这几个问题最常被问,也最容易被答偏
1. 缓存一致性里,默认到底该先删缓存还是先写数据库?
对大多数“数据库是事实来源”的普通业务,默认更推荐 先更新数据库,再在事务提交后删除缓存。但前提是删除失败要有补偿,热点回源要有保护。
2. 为什么数据库都改成功了,接口还能读到旧值?
常见原因包括:
- 删除缓存失败
- 删除发生在事务提交前
- 并发读请求在空窗期把旧值回填回缓存
- 热点 key 上存在多次更新乱序
3. 延迟双删到底值不值得上?
如果业务能容忍短暂旧值,而且你明确遇到的是并发读回填旧值问题,可以作为补丁方案考虑。但不要把它当成一劳永逸的标准答案。
4. 强一致场景是不是就不能用缓存?
不是绝对不能,但不能把关键正确性建立在普通读缓存命中上。更常见的做法是:关键校验走数据库或强一致链路,缓存只承担读扩散或非关键展示角色。
5. 为什么删个缓存,最后会把接口慢也拖出来?
因为删缓存会触发 miss,miss 会触发回源,回源会打数据库,数据库一慢就会继续拖连接池、线程池和接口 RT。这是一条很典型的放大链。
十五、如果你要把现场继续拆下去
如果你最近正卡在“删缓存、写库之后为什么还是出事”,下面这些文章分别补的是不同证据面,不是换个说法把同一套结论再讲一遍。
先补缓存侧这几块
如果回源已经拖到数据库和接口
如果你准备按现场一步步拆
- 先把这篇里的 事务提交时机 / 删缓存失败 / 并发回填旧值 / 回源放大 这条因果链对齐。
- 如果你现在其实还没分清是击穿、穿透还是雪崩,直接去看 Redis 缓存击穿、穿透、雪崩,到底应该怎么区分和处理?
- 如果现场主要卡在少数高热点 key 删除后抖动明显,就转去看 Redis 热 key 问题怎么发现和处理?常见思路讲清楚
- 如果你发现为了压住击穿和旧值回填,缓存越堆越大、TTL 越拉越长,再看 Redis 内存不断上涨,是大 key、碎片率还是过期策略出了问题?
- 如果删缓存后的回源已经把数据库拖慢,就把 MySQL 慢查询怎么定位:从执行计划到真实瓶颈 和 事务执行时间过长,真正拖慢系统的往往不只是数据库 一起带上。
- 如果问题最后表现成接口 RT 上升、连接池和线程池一起抖,再把 接口响应慢怎么排查?后端性能问题定位步骤 一起串上。
十六、最后总结:缓存一致性真正难的,不是“删缓存”这三个字,而是删完以后谁会把系统拖偏
缓存一致性最容易被讲轻的地方在于:大家都知道“更新数据库以后要处理缓存”,但线上出事从来不是因为不知道这句话,而是因为下面这些事同时发生了:
- 删除时机不对
- 事务还没提交
- 删除失败没人补偿
- 并发读把旧值重新写回
- 热点 key 删除后大量回源
- 数据库、连接池、线程池和接口 RT 被一起放大
更实际的判断主线是:
先定业务容忍度,再看写链路时序;确认删除动作是不是落在事务提交后,再去看读路径会不会把旧值写回;最后把热点回源、数据库慢链路和接口慢放到同一张时间线上。
这条线一旦对齐,很多表面上像“缓存老是不同步”的问题,最后都会落回到一个能验证、能补偿、能复盘的工程细节。