缓存不一致主因是失效策略不匹配业务节奏,而非缓存本身故障;需检查cache()写入条件、驱动配置、权限、TTL设置、标签命名规范及并发锁机制。

缓存“没变”不是数据没更新,而是你没让它失效——多数 PHP 缓存不一致问题,根源在失效策略没对上业务节奏,而不是缓存本身坏了。
cache() 函数不刷新?先看它到底有没有写进去
ThinkPHP 的 cache() 在闭包里漏 return、路径不可写、驱动配置为空字符串('')或拼错(如 'fle'),都会导致静默失败:既不报错,也不落盘,读的永远是上次成功的旧值。
- 用
dump(cache('key'))直接看返回值,别靠“应该变了”猜 - 检查
runtime/cache/目录权限,确保 Web 进程用户(如www-data)有w权限 - 运行
php think env:debug或Config::get('cache')确认最终生效的driver配置,避免被config/app.php里的空数组覆盖 - Redis 驱动下,
expire设为0会导致部分版本跳过写入,必须是正整数
主动清除 vs TTL 过期:什么时候该删,什么时候等它自己死
高一致性场景(如用户资料、订单状态)不能靠 TTL 等着过期,必须在数据库写完后立刻调用清除;而文章列表、站点配置这类可容忍几分钟延迟的数据,设 300~3600 秒 TTL 更省心。
- 清除用
Cache::forget('user:123:profile')或cache('user:123:profile', null)(TP5.1+ 中cache('key', '', 0)才是强制清空) - TTL 不是越长越好:Redis 内存满时会按淘汰策略踢数据,
volatile-lru下长期未访问的 key 可能比到期时间更早消失 - 别只设 TTL 就不管了——配合主动清除,相当于加了一道保险:删了再设新 TTL,防堆积
标签(tag)失效失败?90% 是名字写错了
ThinkPHP 的 Cache::tag('user')->clear() 不是模糊匹配,它依赖元数据文件记录键与标签的精确映射。标签名含大写字母、冒号、空格或拼写偏差,就找不到任何缓存项。
立即学习“PHP免费学习笔记(深入)”;
- 标签名必须全小写、无特殊字符:
'user_list'✅,'UserList'❌,'user:list'❌ - 设置带标签的缓存必须显式走
Cache::tag('user')->set('key', $data, 3600),cache('key', $data, 3600, ['tag' => 'user'])是无效的 - Apcu 和 Memcached 驱动压根不支持
tag(),开了也白开;文件和 Redis 驱动才真正生效 - 自定义前缀(如
'prefix' => 'api_v2_')会让Cache::clear()失效,得手动遍历文件或 Redis keys 匹配api_v2_*.php
缓存重建时并发打穿数据库?锁不是可选,是必须
缓存失效瞬间,几十个请求同时发现没命中,全去查数据库——这就是击穿。尤其在热点数据(如首页 Banner、秒杀商品)上,不加控制会直接拖垮 DB。
- 用 Redis 的
SETNX实现简易分布式锁:$redis->set('lock:user:123', 1, ['nx', 'ex' => 5]),成功才查库写缓存 - 锁过期时间要略大于查询+写入耗时,避免锁提前释放导致重复写
- 本地 APCu + Redis 双层缓存可缓解:APCu 命中快、无网络开销,Redis 保全局一致,但注意 APCu 无法跨进程共享,不适合多 worker 场景
- 永不过期 + 定时异步刷新适合报表类数据,但得确保定时脚本执行频率高于业务容忍延迟,比如每 5 分钟跑一次,别卡在整点集体触发
最麻烦的失效问题,往往藏在「没报错」里:驱动没加载、权限不对、标签名大小写不一致、Redis prefix 冲突……这些都不会抛异常,只会让你对着旧数据干瞪眼。动手前先确认缓存真在跑,比调逻辑更重要。



















