ThinkPHP模型查询缓存不能仅靠cache(true),因其只写不读;必须手动统一key生成、显式读取与toArray()序列化存储,否则缓存无效。

ThinkPHP 的模型查询缓存不能靠 cache(true) 一开就完事——它只写不读,根本不算真正缓存。必须手动控制 key 生成、显式读取、配对存储,否则接口里查十次,数据库照样跑十次。
cache(true) 为什么查了十次还走十次数据库
因为 cache(true) 只在查询执行后把结果塞进缓存,但下次调用完全相同的链式查询时,框架不会自动去缓存里找;它没有内置 key 推导逻辑,更不会复用上次的缓存值。
- 错误认知:
UserModel::where('status', 1)->cache(true)->select()执行两次 → 第二次仍查库 - 真实行为:第一次写入缓存(key 是随机或默认生成的),第二次没指定 key,也无自动匹配机制,直接重查
- 后果:缓存成了“单次快照”,和没开一样,尤其在列表页、API 多次调用场景下毫无作用
用 findOrEmpty() + cache tag 实现自动读写闭环
单条记录查缓存最省心的方式是走封装好的方法,它内部已处理 key 生成、反序列化、空值兜底等细节。
- 推荐写法:
UserModel::findOrEmpty($id, ['cache' => ['tag' => 'user']]),会自动生成类似think_model_user_123的 key 并自动读取 - 若需自定义过期时间,加
'expire' => 3600;tag 清除时可批量失效:Cache::tag('user')->clear() - 注意:该方法仅适用于主键查询;非主键(如
where('email', $e))必须手动管理 key
手动缓存要严格对齐 key 和数据形态
手动缓存看似自由,但错一个字符、漏一个 toArray(),就会导致命中失败或反序列化报错。
立即学习“PHP免费学习笔记(深入)”;
- 缓存键必须稳定:推荐用
md5(json_encode($query->getOptions()))或业务语义命名,如'user_list_status_1',避免用time()或随机数 - 查前先
Cache::tag('user')->get($key),未命中再执行$result = $query->select() -
$result是Collection对象,必须转成数组再存:Cache::tag('user')->set($key, $result->toArray(), 3600),否则反序列化失败 - 缓存驱动别用
file:高并发下文件锁会导致阻塞,生产环境务必切到redis
distinct() 和 group() 查询不能直接套 cache(true)
带 distinct(true) 或 group() 的查询,其缓存 key 容易因字段顺序、空格、别名差异而错位,且结果结构不稳定(如含聚合字段),手动缓存风险更高。
- 典型陷阱:
Db::name('order')->distinct(true)->field('user_id')->cache(true)->select()→ 每次生成的 key 可能不同,缓存形同虚设 - 安全做法:改用固定 key + 显式缓存,例如
$key = 'order_distinct_user_ids'; $data = Cache::tag('order')->get($key) ?: Cache::tag('order')->set($key, Db::name('order')->distinct(true)->field('user_id')->select()->toArray(), 1800) - group() 类查询更要警惕 MySQL 5.7+ 的
ONLY_FULL_GROUP_BY模式,缓存前务必确认 SQL 能稳定执行
缓存不是开关,是契约:写的时候怎么拼 key、怎么序列化,读的时候就得一模一样。最容易被忽略的是 Collection 直接缓存和 file 驱动在并发下的表现——这两点一出问题,缓存反而变成性能负资产。



















