模型查询缓存不能靠cache(true)自动生效,因其缓存键不含实际参数值,易导致不同条件查询复用错误缓存;必须显式传入含参缓存键(如['user_list', $status, $type])并配合标签清理才能精准控制。

模型查询缓存不能靠 cache(true) 自动生效,尤其带 where 条件、with 关联或动态参数时,它大概率会复用错的缓存结果。
cache(true) 为什么经常缓存错?
ThinkPHP 模型默认缓存键只含类名、方法名和 SQL 模板,不含实际绑定的参数值。比如两次调用:
UserModel::where('status', 1)->cache(true)->select();
UserModel::where('status', 2)->cache(true)->select();
它们生成的缓存键很可能一样,导致第二次查到的是 status=1 的旧数据。
- 不是 bug,是设计如此:缓存键不解析运行时变量
-
cache(true)只适合「完全静态」查询(如固定 ID 的find(1)) - 用了
scope、when或拼接条件,必须手动构造缓存键 - 关联查询
with('profile')不参与键计算,不同关联组合会互相污染
怎么写才真正按条件区分缓存?
唯一可靠方式是显式传入含参的缓存键,让键名能反映实际查询语义:
- 用数组方式最安全:
cache(['user_list', $status, $type], 300),TP 会自动md5(serialize($array))生成稳定字符串 - 避免直接拼字符串:
cache('user_list_' . $status . '_' . $type),容易因空格、特殊字符或类型差异(如1vs'1')导致键不一致 - 对分页场景,把
$page和$limit也塞进数组键里,否则第 1 页和第 2 页可能命中同一个缓存 - 别在模型静态方法封装里隐式调用
cache(true),它无法感知外部传参变化
关联查询缓存怎么单独控制?
with() 本身不缓存,必须在关联定义里显式加 cache():
public function profile()
{
return $this->hasOne(Profile::class)
->cache(['user_profile', $this->id], 3600);
}
- 这样
UserModel::with('profile')->find(123)才会缓存Profile数据,且键含主模型 ID - 如果关联表更新了但主模型缓存没失效,会读到脏数据;此时应配合
Cache::tag('profile')->clear()主动清理 - 事务中刚写入关联数据就立即
with(),建议临时禁用:->cache(false),避免读到旧缓存 - 软删除字段(如
delete_time)未在关联缓存键中体现,逻辑删除后缓存仍有效,需额外处理
标签清除为什么经常失效?
很多人以为 UserModel::where(...)->cache('user')->select() 中的 'user' 是缓存标签,其实它只是缓存键前缀,底层根本没调用 Cache::tag()。
- 只有
Cache::tag('user')->set($key, $value)存的数据,才能用Cache::tag('user')->clear()清掉 - 模型的
cache('user')最终生成键类似think:UserModel:xxx,和标签无关 - 跨缓存驱动不共享标签:用
Cache::store('redis')->tag('user')存的,不能用Cache::store('file')->tag('user')清 - File/Redis/Memcached 驱动支持标签,Apcu/Xcache 不支持,调用
tag()会静默退化为普通缓存
真正要按组清理,得绕过模型 cache(),改用手动序列化 + Cache::tag() 存取——这点最容易被跳过,结果清缓存时发现什么都没发生。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











