Redis缓存需先确认数据库查询为真瓶颈,再针对高频读低频写场景缓存语义明确的中间结果;须显式配置Redis驱动,用Cache::remember()保证原子性,key含业务标识,tag批量失效,空结果与过期抖动防穿透雪崩。

Redis 缓存查询结果前,先确认 Db::name() 查询是否真成了瓶颈
很多同学一上来就加 Redis,结果发现接口没快多少——因为真正拖慢响应的其实是没加索引的 WHERE 条件、N+1 查询,或者 JOIN 里用了函数导致索引失效。先用 ThinkPHP 的日志或 Db::getLastSql() 把慢查询揪出来,再看执行计划(EXPLAIN)。
只有当同一条件的 select * from user where status=1 limit 20 每秒被调几十次、且数据变化不频繁时,缓存才有意义。否则,缓存反而增加维护成本和一致性风险。
- 高频读 + 低频写(如配置表、地区字典)适合缓存;用户订单列表这种带登录态、分页参数多的,缓存 key 难设计,慎用
- 不要缓存整个
Db::name('user')->select()返回的数组,而应缓存「可复用的中间结果」,比如getTop10UsersByScore()这类明确语义的方法返回值 - ThinkPHP 6.1+ 的
cache()方法默认走 file 驱动,必须显式配置redis才生效,别以为写了cache(3600)就自动上 Redis
用 Cache::remember() 替代手写 if-else 缓存逻辑
手动判断 key 是否存在、查库、写入缓存,三步写错一步就漏缓存或写错过期时间。ThinkPHP 内置的 Cache::remember() 自动处理原子性,避免并发重复写入。
它本质是「先查缓存,命中直接返回;未命中则执行闭包、写入缓存并返回」,底层已加锁防击穿(需开启 cache.redis.handler = \think\cache\driver\Redis 并配置 serialize = php)。
立即学习“PHP免费学习笔记(深入)”;
- 正确写法:
use think\Cache;<br>return Cache::remember('user_list_status_1', function () {<br> return Db::name('user')->where('status', 1)->limit(20)->select();<br>}, 3600); - 错误写法:用
Cache::get()+Cache::set()分两步,高并发下可能多次执行闭包 - key 名必须含业务标识(如
status值),不能写死成'user_list',否则不同条件共用一个缓存会出错
Cache::tag() 是批量失效的唯一靠谱方式,别用通配符删 key
用户更新了资料,你得让所有以他 ID 开头的缓存(如 user_profile_123、user_orders_123)全部失效。Redis 原生不支持模糊删 key,KEYS user_* 在生产环境禁用(阻塞主线程)。
Cache::tag() 通过在写入缓存时打标(内部用 set 存 tag → key 映射),删 tag 就等于删一批 key,安全高效。
- 写缓存时打标:
Cache::tag('user_123')->set('user_profile_123', $data, 3600);<br>Cache::tag('user_123')->set('user_orders_123', $list, 1800); - 更新后一键清理:
Cache::tag('user_123')->clear() - 注意:tag 功能依赖 Redis 的
set和smembers命令,若用的是云 Redis(如阿里云 Tair),需确认是否支持,部分精简版不兼容
缓存穿透、雪崩不是理论问题,empty 结果也得缓存
攻击者用大量不存在的 user_id=999999999 请求,每次都会穿透到数据库。ThinkPHP 默认对 null 或空数组不缓存,必须显式存一个占位符。
更麻烦的是缓存雪崩:大量 key 同一时刻过期,瞬间全量回源。解决方案不是简单加随机数,而是结合业务节奏设置基础过期时间 + 小范围抖动。
- 空结果缓存示例:
$data = Db::name('user')->find($id);<br>if (is_null($data)) {<br> Cache::set("user_{$id}", '__EMPTY__', 600); // 存个标记,10分钟<br>} else {<br> Cache::set("user_{$id}", $data, 3600);<br>} - 过期时间抖动:基础 3600 秒,再加
rand(0, 300),但别在每次请求里算——应在缓存写入时一次性确定,否则下次读还是可能撞上同一秒过期 - 别把缓存时间设成「永不过期」,Redis 内存有限,且
maxmemory-policy配置不当会导致 LRU 踢掉热点数据
缓存不是开关,是需要持续观测的组件。上线后盯紧 Redis 的 used_memory、evicted_keys 和 expired_keys 指标,比代码里多加一行 Cache::tag() 重要得多。



















