先删缓存再更新数据库在ThinkPHP中易出错,因高并发下请求B可在A删缓存后、写库前读库并回填旧值,且Cache::delete与Db::update无原子性、Redis删失败常被忽略、多级缓存只删一级,导致脏数据长期滞留。

先删缓存再更新数据库,为什么在ThinkPHP里容易出错
这个顺序看似合理,但实际在高并发下会暴露明显漏洞:请求A删完缓存还没来得及写库,请求B就进来查库拿到旧值并回填缓存,导致缓存中存的是过期数据。Cache::delete($key) 和 Db::update() 之间没有原子性保障,中间插入的读请求就是一致性破口。
ThinkPHP 默认不提供跨操作事务包装,你没法像数据库那样用 beginTransaction() 把缓存操作和 DB 操作锁在一起。更麻烦的是,Redis 删除失败时不会抛异常(比如连接超时),Cache::delete() 返回 false 但业务代码常忽略这个返回值,缓存就漏删了。
- 删除缓存失败后无重试或告警机制,脏数据长期滞留
- DB 更新成功但 Redis 不可达时,缓存状态完全失控
- 多级缓存(如 APCu + Redis)只删一级,另一级仍返回旧值
ThinkPHP 中真正能落地的写操作策略:先更新数据库,再异步删缓存
这个顺序把“数据源头”放在最前,哪怕缓存没删干净,最多只是短暂多缓存一次旧值;而旧值会在下次读请求时被自然覆盖——前提是读逻辑用了 Cache::remember() 或带 EX 的原子写入。
关键不是“删不删”,而是“删得稳不稳”。ThinkPHP 8+ 支持事件监听,推荐用 Db::event('AfterUpdate') 触发异步清理,而不是在业务方法里硬编码 Cache::delete():
立即学习“PHP免费学习笔记(深入)”;
Db::event('AfterUpdate', function ($query, $data) {
$key = 'user:' . $data['id'];
// 异步队列或延迟任务,避免阻塞主流程
dispatch(new DeleteCacheJob($key));
});
- 异步删缓存可容忍 Redis 短暂不可用,失败后进队列重试
- 避免在事务中混入 Redis 操作,防止 DB 事务回滚但缓存已删
- 配合
Cache::remember($key, $ttl, fn() => Db::find(...)),确保读请求自动兜底刷新
缓存键设计与 TTL 必须配合更新策略
很多问题其实不出在“删不删”,而出在“删哪个”和“删多久之后还有效”。ThinkPHP 默认生成的缓存键如果没加业务前缀、没对参数排序哈希,会导致同一数据多个 key 并存,删了一个另一个还在生效。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
例如用户信息更新后只删了 user:123,但实际还有 user_detail:123、cache_user_123_v2 等变体键没清理。TTL 不设抖动也会引发雪崩:所有用户缓存都在整点过期,瞬间打穿 DB。
- 键名强制统一前缀:
config('cache.prefix') . ':user:' . md5(json_encode($params)) - TTL 加随机偏移:
$ttl = 3600 + random_int(1, 300),避免集体失效 - 涉及关联数据(如用户+订单+地址),删缓存时用通配符或 tag 方式批量清理(需 Redis 6.0+ 或自建映射表)
多级缓存同步必须显式双删,TP 不会帮你做
启用 APCu + Redis 双层缓存后,Cache::delete($key) 默认只作用于当前驱动(通常是 Redis),APCu 里的副本依然存在。后续请求走 Cache::get() 会优先命中本地内存,拿到的就是旧值。
ThinkPHP 不自动维护多级一致性,你必须自己控制删哪几层:
Cache::store('apcu')->delete($key);
Cache::store('redis')->delete($key);
更稳妥的做法是封装一个 MultiLevelCache::purge($key),把所有启用的缓存驱动都遍历一遍。注意 APCu 是进程级的,删完本进程有效,但其他 FPM worker 还得靠 TTL 自然过期——所以 APCu 的 TTL 要比 Redis 更短,且不能设为永不过期。
真正难的从来不是“选哪个策略”,而是每个策略背后要补多少细节:连接容错、键管理、多级协同、失败兜底。这些地方一漏,再标准的 Cache Aside 也撑不住日均百万级请求。


















