不能先删缓存再更新数据库,因为并发下极易出现脏数据:A删缓存后未及写库,B读空缓存并回源写入旧值,A再更新数据库,缓存仍为旧值;正确起点是先更新数据库再删缓存,并需同步重试删除失败、验证删除结果、确保键名一致。

为什么不能先删缓存再更新数据库
因为并发请求下极易出现脏数据:请求 A 删除缓存后,还没来得及写库,请求 B 就读到空缓存,回源查出旧值并写入缓存;等 A 完成数据库更新,缓存里仍是旧值。DeleteCache 和 UpdateDB 之间存在时间窗口,这是双写不一致的根源之一。
双写一致性真正可行的起点是「先更新数据库,再操作缓存」,但仅靠这个还不够——必须处理好失败分支。
更新数据库成功后,删缓存还是设新值
推荐优先用 DeleteCache(缓存失效),而非 SetCache(主动写缓存)。原因有三:
- 避免缓存与数据库字段不一致:比如 DB 更新了 3 个字段,但代码只塞了 2 个进缓存
- 减少序列化开销:尤其对大结构体,
json.Marshal可能成为瓶颈 - 规避并发覆盖:两个更新请求几乎同时完成,后设的缓存可能反而是旧快照
例外情况是读远多于写、且结构稳定的小对象,此时可考虑 SetCache 降低穿透压力,但必须保证序列化逻辑和 DB 查询逻辑完全同源。
删缓存失败时必须重试,但不能无限重试
常见错误是把 DeleteCache 当作可丢弃的异步操作,或只做一次尝试。一旦失败,缓存就长期脏了。
实操建议:
- 使用带最大重试次数(如 3 次)和指数退避的同步重试,失败后记录日志和指标(如
cache_delete_failure_total) - 不要在 HTTP handler 中阻塞重试太久,可降级为发消息到队列(如 Kafka / Redis Stream),由后台消费者保底清理
- 务必检查
DeleteCache的返回值:Redis 的DEL返回int64,0 表示 key 不存在,非 0 或 error 才算异常
如何验证缓存真的被清除了
开发期容易忽略验证环节,导致“以为删了,其实没删”。关键点:
- 在
DeleteCache后立即GetCache,断言返回nil或redis.Nil(不是空字符串或零值) - 注意 Redis 的
GET和 Go redis client 的Get(ctx, key).Result()对不存在 key 的返回差异:前者是(nil, nil),后者是("", redis.Nil) - 集成测试中模拟 DB 更新 → 缓存删除 → 再读 → 验证走 DB 而非缓存,用
mockDB.ExpectQuery和mockRedis.ExpectDel双向校验
最常被跳过的其实是缓存键构造逻辑的一致性:DB 更新用的是 "user:123",而删除时拼成了 "user_id_123",这种低级错误在线上极难排查。


















