ThinkPHP 的 with() 关联查询默认不缓存,主模型缓存不影响关联数据加载;需手动用 Cache::remember() 或 relation() 结合缓存控制,并主动管理缓存失效。

ThinkPHP 的 with() 关联查询默认不缓存
直接调用 with() 加载关联数据(比如 User::with('profile')->find(1))时,ThinkPHP 不会自动缓存关联结果。哪怕你对主模型启用了查询缓存(如 cache(true)),关联部分仍会重新查库 —— 这是很多人踩坑的起点。
原因很简单:with() 底层是通过额外的 SQL 查询(JOIN 或子查询 + 单独 SELECT)加载关联,而 ThinkPHP 的模型缓存机制只作用于当前模型的主查询,不递归穿透到关联模型。
- 主模型加了
cache(3600),只缓存User本身那条记录 -
profile关联仍每次执行SELECT * FROM user_profile WHERE user_id = 1 - 如果页面频繁访问、关联表大或网络延迟高,性能损耗明显
手动缓存关联数据:用 Cache::remember() 包裹关联查询
最可控的方式,是绕过 with() 的自动行为,自己把关联逻辑拆出来,用缓存兜底。适合关联关系明确、ID 可预知的场景(比如查用户详情页)。
示例:缓存用户及其 profile
立即学习“PHP免费学习笔记(深入)”;
// 不要用 User::with('profile')->find(1)
$user = User::find(1);
$profile = Cache::remember('user_profile_' . $user->id, 3600, function () use ($user) {
return UserProfile::where('user_id', $user->id)->find();
});
- 缓存键必须带主模型 ID,避免冲突;别用固定字符串如
'profile' - 注意
Cache::remember()的第三个参数是闭包,use ($user)必须显式传入,否则闭包里拿不到变量 - 如果关联是一对多(如
orders),改用all()并确保缓存值是数组:return Order::where('user_id', $user->id)->select();
用 relation() + 自定义缓存逻辑替代 with()
如果你仍想保留模型关系定义(比如在 User 模型里写了 public function profile()),但又不想每次走 DB,可以用 relation() 手动触发关联,并自行控制缓存读写。
关键点:不要依赖 with() 的懒加载或预加载,而是显式调用关系方法 + 缓存封装:
$user = User::find(1);
$profile = Cache::get('user_profile_' . $user->id);
if (is_null($profile)) {
$profile = $user->profile()->find();
Cache::set('user_profile_' . $user->id, $profile, 3600);
}
-
$user->profile()返回的是 Query 对象,->find()才真正执行查询;这比with()更灵活,也更容易插缓存 - 注意检查
$profile是否为 null(包括缓存未命中和 DB 确实无数据),避免重复写入空值 - 如果关联模型也用了软删除,记得在查询里加
->withoutTrashed(),否则缓存可能存入已被删除的数据
缓存失效时机必须手动管理
ThinkPHP 不会自动监听关联表变更来清理主模型缓存 —— 这是最容易被忽略的一环。用户更新了 profile,但 User::find(1) 仍从缓存返回旧 profile,问题就来了。
- 在
UserProfile模型的save()或delete()后,主动删缓存:Cache::delete('user_profile_' . $this->user_id) - 如果用事件(如
afterWrite),确保监听的是关联模型,不是主模型 - 别依赖「主模型缓存过期时间」来兜底,业务上对一致性有要求时,必须主动失效
缓存键设计、失效同步、null 值处理,这三个点漏掉任何一个,关联缓存就等于没做。



















