关联查询缓存不能靠remember()自动穿透,必须显式设计缓存键并拆分主模型与关联模型缓存,否则易缓存空数组、漏清缓存或导致数据不一致。

关联查询缓存不能靠 remember() 自动穿透,必须显式控制缓存键和数据结构,否则容易缓存空数组、漏清缓存或重复查库。
Cache::remember() 不会自动缓存 with() 关联数据
remember() 只缓存闭包最终返回值,它不管你是怎么查的。比如 Post::with('user')->where('status', 'published')->get() 这个链式调用,remember() 缓存的是整个集合对象(含已加载的 user),但不会单独为 user 关系建缓存键,也不会感知 User 模型后续被修改。
- 缓存命中时直接反序列化整个结果,
user属性是“快照”,不是实时关系 - 如果只改了某个
User的名字,Post列表缓存不会失效,用户看到的还是旧昵称 - 不要写
remember(...)->with('user')然后以为关联也自动受控——它只是“顺便带上了”,不是“受保护了”
手动缓存关联数据需拆开处理
想让 user 关系本身可独立失效,就得把主模型和关联模型分开缓存,再在应用层组装。典型做法是:先查主模型 ID 列表,再批量查关联,最后合并。
- 用
Cache::remember('posts_ids_published', 3600, fn() => Post::where('status', 'published')->pluck('id'))缓存 ID 列表 - 再用
Cache::remember('users_by_ids_' . md5(implode(',', $ids)), 3600, fn() => User::whereIn('id', $ids)->pluck('name', 'id'))缓存用户映射 - 最后在 PHP 中用
$posts->map(fn($p) => $p->merge(['user_name' => $users[$p->user_id] ?? null]))组装 - 这样
users_by_ids_*键可被User模型更新事件精准清除,不影响主列表缓存
laravel-cacheable-model 扩展对关联的支持有限
该扩展默认只代理模型自身的查询(如 Post::find() 或 Post::where()->get()),对 with()、load()、belongsTo() 动态加载等不生效。
- 即使你在
Post模型中用了use CacheableTrait,$post->user第一次访问仍会走数据库(除非你额外给User也加CacheableTrait) - 它不拦截 Eloquent 的懒加载逻辑,所以
with('user')和$post->user是两套行为 - 若真要全链路缓存,必须每个关联模型都启用该 trait,并且确保缓存键包含外键值(如
user:123)
缓存键设计必须包含所有影响输出的字段
关联查询结果是否一致,不仅取决于主模型条件,还取决于关联模型的筛选、排序、字段选择。键名漏掉任一变量,就会导致缓存污染。
- 错例:
Cache::remember('posts_with_user', ...)—— 完全静态键,不同分页、不同用户角色都共用一个缓存 - 对例:
Cache::remember("posts_with_user:page_{$page}:role_{$role}:sort_{$sort}", ...) - 特别注意:
withCount()、withSum()等聚合关联,也要把聚合字段名纳入键(如with_count_comments) - 如果关联里用了
select()限定字段,键中也得体现,否则字段增减会导致反序列化失败或数据错位
最常被忽略的一点:缓存关联数据时,remember() 闭包里不能混用未缓存的动态查询。比如在闭包里先查 Post,再用循环逐条查 User::find($p->user_id),这会把 N+1 问题锁进缓存,下次命中反而更慢。


















