写操作必须同步失效缓存:为什么"改了却没生效"
系统里有个”部门人数”统计,为了性能加了 60 秒内存缓存。某天做”离职”操作,人明明走了,用户刷新一看人数没变;过一会儿再看,人又”消失”了。同一天里同一个数字两次不一致。
这不是性能问题,是架构问题:给接口加缓存之后,缓存就变成了一条必须维护的消费端。
问题长什么样
典型场景:
- 一个读接口被频繁访问,于是加了缓存(key 按查询参数,TTL 60 秒)
- 一系列写操作会改变这个接口的底层数据:新增、删除、批量导入、重新计算
- 写操作成功了,但没清缓存 → 读接口继续吐旧数据
- 直到 TTL 过期或 key 变化,用户才看到新数据
用户视角就是一句”改了为什么还没好”。等 TTL 过期?60 秒都等不起,更别说用户会手动刷新触发各种不可预期的顺序。
两个同类 bug
案例一:离职/入职没清缓存。 “部门人数”缓存 key 只含时间段参数,但”有没有人离职”跟时间段无关——单靠换 key 根本挡不住。离职接口成功返回,缓存还是旧的,人还在里面。直到用户切到别的时间段再切回来,缓存过期重查,人才消失。同一个操作,两次看到两个不同的结果。
案例二:改 AI 生成结论的提示词没清缓存。 把大模型结论的生成规则改了(比如禁用一个指标),服务重启了,但结论缓存表里的旧记录还在,页面读缓存直接显示,用户看到的还是改之前的结论。代码层完全正确——提示词对、数据对、单测过、服务重启了——但用户视角就是”反复改都没好”。
两个案例同一个根因:写操作(改数据 / 改口径)与缓存失效脱钩了。
五条设计原则
| # | 原则 | 做法 |
|---|---|---|
| 1 | 写操作 = 缓存失效点 | 设计功能点时先问:这个数据被谁缓存?哪些写操作会改变它?列成”写操作 × 消费端缓存”清单,一个不漏 |
| 2 | 写操作统一走失效函数 | 项目里有缓存,就建一个 invalidate_xxx_cache(),所有写操作成功后可调用,不要散落一堆 cache.clear() |
| 3 | 缓存 key 覆盖不了所有维度 | key 只能按查询参数区分,但影响数据的维度(新增 / 删除 / 重算)往往跟查询参数无关——必须显式失效 |
| 4 | TTL 只是兜底,不是方案 | 永远不要说”等过期”。用户的预期是”改了马上看到” |
| 5 | 影响面清单把缓存算进去 | 大改动列影响面时,缓存消费端要进清单,详见需求拆分防扩散 |
上线前的触发三问
任何写操作上线前,先回答:
1. 这个写操作改了哪些数据?(新增?删除?修改属性?重算结论?)
2. 哪些接口 / 页面缓存了这些数据?(内存缓存?持久化缓存表?TTL 多久?)
3. 写操作成功后缓存失效了吗?(有失效函数吗?没有就补上。)
结论
“改了没生效”这一类 bug 的共同点,是改了源头 ≠ 改了运行时 ≠ 改了展示链路的旧状态。代码正确只是第一步,展示端的历史缓存不清,用户看到的永远是新代码之前的旧世界。
写操作与缓存失效必须同步,TTL 永远只是兜底。
支持与分享
如果这篇文章对你有帮助,欢迎分享给更多人或打赏支持!







