写操作必须同步失效缓存:为什么"改了却没生效"

936 字
5 分钟
写操作必须同步失效缓存:为什么"改了却没生效"

系统里有个”部门人数”统计,为了性能加了 60 秒内存缓存。某天做”离职”操作,人明明走了,用户刷新一看人数没变;过一会儿再看,人又”消失”了。同一天里同一个数字两次不一致。

这不是性能问题,是架构问题:给接口加缓存之后,缓存就变成了一条必须维护的消费端。

问题长什么样#

典型场景:

  1. 一个读接口被频繁访问,于是加了缓存(key 按查询参数,TTL 60 秒)
  2. 一系列写操作会改变这个接口的底层数据:新增、删除、批量导入、重新计算
  3. 写操作成功了,但没清缓存 → 读接口继续吐旧数据
  4. 直到 TTL 过期或 key 变化,用户才看到新数据

用户视角就是一句”改了为什么还没好”。等 TTL 过期?60 秒都等不起,更别说用户会手动刷新触发各种不可预期的顺序。

两个同类 bug#

案例一:离职/入职没清缓存。 “部门人数”缓存 key 只含时间段参数,但”有没有人离职”跟时间段无关——单靠换 key 根本挡不住。离职接口成功返回,缓存还是旧的,人还在里面。直到用户切到别的时间段再切回来,缓存过期重查,人才消失。同一个操作,两次看到两个不同的结果。

案例二:改 AI 生成结论的提示词没清缓存。 把大模型结论的生成规则改了(比如禁用一个指标),服务重启了,但结论缓存表里的旧记录还在,页面读缓存直接显示,用户看到的还是改之前的结论。代码层完全正确——提示词对、数据对、单测过、服务重启了——但用户视角就是”反复改都没好”

两个案例同一个根因:写操作(改数据 / 改口径)与缓存失效脱钩了。

五条设计原则#

#原则做法
1写操作 = 缓存失效点设计功能点时先问:这个数据被谁缓存?哪些写操作会改变它?列成”写操作 × 消费端缓存”清单,一个不漏
2写操作统一走失效函数项目里有缓存,就建一个 invalidate_xxx_cache(),所有写操作成功后可调用,不要散落一堆 cache.clear()
3缓存 key 覆盖不了所有维度key 只能按查询参数区分,但影响数据的维度(新增 / 删除 / 重算)往往跟查询参数无关——必须显式失效
4TTL 只是兜底,不是方案永远不要说”等过期”。用户的预期是”改了马上看到”
5影响面清单把缓存算进去大改动列影响面时,缓存消费端要进清单,详见需求拆分防扩散

上线前的触发三问#

任何写操作上线前,先回答:

1. 这个写操作改了哪些数据?(新增?删除?修改属性?重算结论?)

2. 哪些接口 / 页面缓存了这些数据?(内存缓存?持久化缓存表?TTL 多久?)

3. 写操作成功后缓存失效了吗?(有失效函数吗?没有就补上。)

结论#

“改了没生效”这一类 bug 的共同点,是改了源头 ≠ 改了运行时 ≠ 改了展示链路的旧状态。代码正确只是第一步,展示端的历史缓存不清,用户看到的永远是新代码之前的旧世界。

写操作与缓存失效必须同步,TTL 永远只是兜底。

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或打赏支持!

打赏
写操作必须同步失效缓存:为什么"改了却没生效"
https://heaven-1314.github.io/posts/write-op-must-invalidate-cache/
作者
赵培州
发布于
2026-08-05
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
赵培州
AI 应用研发工程师 · 用 Agent 做默认交付
公告
记录 AI 应用落地实践与踩坑经验,欢迎交流。
分类
标签
最新动态
站点统计
文章
32
分类
9
标签
81
总字数
41,808
运行时长
0
最后活动
0 天前
站点信息
构建平台
GitHub Actions
博客版本
Firefly v6.15.6
文章许可
CC BY-NC-SA 4.0