thinkphp查询缓存必须显式调用cache()且紧邻终端方法(如select/find)之前,不自动生效;默认不缓存、键易冲突、关联与聚合不支持、更新后须手动清理。

ThinkPHP 的查询缓存不是“开了就自动生效”的功能,必须显式调用且位置严格;模型默认不缓存、Db 查询不自动带标签、cache() 传参错一位就失效——这些是绝大多数人踩坑的起点。
cache() 方法必须放在 select/find 前,且不能链式提前调用
很多人写 Db::table('user')->where('id', 1)->_cache()->select(),直接报 Fatal error: Call to undefined method think\db\Query::_cache()。因为 _cache() 不是 Query 类的原生方法,而是执行前临时注入的钩子,只对紧邻其后的 select()、find()、value() 等终端方法生效。
-
_cache()必须紧跟在查询构建末尾、终端方法之前,例如:->where()->order()->_cache(['key', 1800, 'user'])->select() - 参数必须是数组,顺序固定:第一个是缓存键(字符串或数组),第二个是秒级过期时间(
null表示用配置默认值),第三个是标签名(可选,但批量清理必须靠它) - 模型上的
cache()方法同理:必须写成UserModel::where('status', 1)->cache('user_list_1', 3600)->select(),不能写成->cache()->where() - 如果用了 scope 或动态条件,别依赖
cache(true)自动生成键——它不包含实际参数值,极易导致不同条件命中同一缓存
缓存键冲突比你想象得更常见,手动构造才是稳解
TP 默认用 SQL 模板 + 类名生成缓存键,但只要 SQL 字符串里有空格差异、变量拼接方式不同、数据库前缀不一致,MD5 后的 key 就会变。结果就是缓存写入了却读不到,或者读到的是别人的数据。
- 推荐用数组方式构造键:
cache(['user_list', $status, $type], 300),TP 会自动serialize()并哈希,稳定且可读 - 避免字符串拼接时漏转义,比如
'user_list_' . $status遇到$status = "1 OR 1=1"不仅键乱,还可能引发安全问题 - 关联查询(
with())不参与键生成,UserModel::with('profile')->cache(true)->find(123)和UserModel::find(123)可能共用一个 key,导致 profile 数据为空 - 聚合函数如
count()返回标量,不能链式调用cache(),会报Call to a member function cache() on int,得改用Cache::remember()封装
按标签清除缓存?模型自带 cache() 根本不支持
写 UserModel::cache('user')->select() 后再执行 Cache::tag('user')->clear(),完全没效果。因为 cache('user') 只是设了个键前缀(最终 key 类似 think:UserModel:xxx),底层根本没调用缓存驱动的 tag() 接口。
- 真正支持标签的只有显式调用
Cache::tag('user')->set($key, $value)的方式 - 要让模型查询走标签缓存,必须绕过
cache(),手动控制:$key = 'user_active_'. $status; $data = Cache::tag('user')->get($key); if (is_null($data)) { $data = UserModel::where('status', $status)->select()->toArray(); Cache::tag('user')->set($key, $data, 3600); } - 标签功能依赖驱动实现
TagSetInterface:File、Redis、Memcached 支持;APCu、Xcache 不支持,调用tag()会静默退化为普通缓存 - 不同缓存 store 实例间标签不共享,
Cache::store('redis')->tag('user')存的,不能用Cache::store('file')->tag('user')清
数据更新后缓存不清理,是最隐蔽的一致性陷阱
TP 的查询缓存是纯“读缓存”,不监听 insert/update/delete。哪怕你刚用 Db::update() 改了用户状态,只要没手动清缓存,下次 select() 还会返回旧数据。
- 所有写操作后必须显式清理:改单条用
Cache::rm('user_list_1'),改一批用Cache::tag('user')->clear() - 开发环境
app_debug = true时,查询缓存默认被跳过,容易误判“功能没生效” - 软删除(
SoftDelete)不会触发缓存失效,delete_time字段变了,缓存里的数据还是“未删除”状态 - 不要在多个模块里复用同一个缓存键(比如都用
'user_list'),否则 A 模块更新后清了,B 模块立刻查不到;建议键中带上模块标识或接口路径
缓存键怎么生成、标签是否真生效、写完数据有没有清——这三件事必须每处都人工核对,框架不会替你兜底。尤其跨服务共用 Redis 时,prefix 配置、select 库号、密码空值处理,任何一个细节漏掉,Cache::get() 就永远返回 null。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











