Laravel嵌套关联同一用户模型的重复查询优化方案问询
优化Laravel Eloquent关联查询中的重复用户查询问题
嘿,这个场景在Laravel开发里真的很常见,咱们先明确两个核心问题,再给出具体的优化方案:
一、这种重复查询是否正常?
答案是:这是Eloquent eager loading的默认正常行为。
默认情况下,Eloquent会为每个关联关系单独执行查询:
- 首先查询当前新闻的作者(
user关联) - 然后查询所有评论的作者(
comments.user关联) - 再查询所有评论回复的作者(
comments.replies.user关联)
哪怕某个用户ID同时出现在新闻作者、评论作者、回复作者里,Eloquent的默认逻辑还是会把这些ID都包含到对应的IN子句中去查询——数据库层面确实会出现重复查询同一ID的情况(不过数据库本身会有缓存,性能影响不会特别大,但确实可以优化)。
你现在手动指定关联字段的做法已经很棒了——这能减少查询返回的数据量,是优化的第一步。
二、如何优化重复查询?
根据你的Laravel版本,有两种更高效的优化方式:
1. 用withUnique(Laravel 9.26+ 推荐)
Laravel 9.26版本新增了withUnique方法,专门用来解决关联查询中的重复加载问题。它会自动对关联查询的ID去重,避免同一个用户被多次查询。
修改你的代码如下:
$news = News::with( 'user:id,name', 'comments:id,commentable_id,content,ip,user_id,created_at,updated_at', // 对评论作者去重查询 'comments.user:id,name' => fn($query) => $query->unique(), 'comments.replies:id,commentable_id,parent_id,content,ip,user_id,created_at,updated_at', // 对回复作者去重查询 'comments.replies.user:id,name' => fn($query) => $query->unique(), // 媒体关联也可以用同样方式去重 'comments.replies.user.media:id' => fn($query) => $query->unique(), 'comments.user.media:id' => fn($query) => $query->unique(), 'comments.reactions:user_id,comment_id,reaction', 'comments.replies.reactions:user_id,comment_id,reaction') ->where('id', $id) ->first(['id', 'heading', 'author_user_id', 'content', 'created_at', 'updated_at']);
或者更简洁的写法,直接使用withUnique方法:
$news = News::with([ 'user:id,name', 'comments:id,commentable_id,content,ip,user_id,created_at,updated_at', 'comments.replies:id,commentable_id,parent_id,content,ip,user_id,created_at,updated_at', 'comments.reactions:user_id,comment_id,reaction', 'comments.replies.reactions:user_id,comment_id,reaction' ]) ->withUnique([ 'comments.user:id,name', 'comments.replies.user:id,name', 'comments.user.media:id', 'comments.replies.user.media:id' ]) ->where('id', $id) ->first(['id', 'heading', 'author_user_id', 'content', 'created_at', 'updated_at']);
这样就能确保每个用户(以及媒体)只会被查询一次,彻底解决重复查询的问题。
2. 手动合并用户查询(兼容Laravel 8及以下版本)
如果你的Laravel版本低于9.26,可以手动收集所有需要的用户ID,一次性查询后再关联到模型上:
// 第一步:先获取新闻和非用户类的关联数据 $news = News::with([ 'comments:id,commentable_id,content,ip,user_id,created_at,updated_at', 'comments.replies:id,commentable_id,parent_id,content,ip,user_id,created_at,updated_at', 'comments.reactions:user_id,comment_id,reaction', 'comments.replies.reactions:user_id,comment_id,reaction' ])->where('id', $id)->first(['id', 'heading', 'author_user_id', 'content', 'created_at', 'updated_at']); // 第二步:收集所有需要查询的用户ID(去重) $userIds = collect([ $news->author_user_id, // 新闻作者ID ...$news->comments->pluck('user_id'), // 所有评论作者ID ...$news->comments->flatMap(fn($comment) => $comment->replies->pluck('user_id')) // 所有回复作者ID ])->filter() // 过滤掉空ID ->unique(); // 去重 // 第三步:一次性查询所有需要的用户和他们的媒体 $users = User::with(['media:id']) ->whereIn('id', $userIds) ->select('id', 'name') ->get() ->keyBy('id'); // 用ID做键,方便快速查找 // 第四步:手动把用户关联到对应的模型上 $news->setRelation('user', $users->get($news->author_user_id)); foreach ($news->comments as $comment) { $comment->setRelation('user', $users->get($comment->user_id)); foreach ($comment->replies as $reply) { $reply->setRelation('user', $users->get($reply->user_id)); } }
这种方式虽然代码量多一点,但完全控制了查询过程,确保只执行一次用户查询,性能最优。
额外提醒
- 始终确保关联查询中包含外键字段(比如你在
comments的select里包含了user_id),否则Eloquent无法正确关联模型,这点你已经做得很好了。 - 如果用户的
media关联也存在重复加载的情况,同样可以用上面的两种方法去优化。
内容的提问来源于stack exchange,提问作者arkert
相关产品推荐
相关产品推荐

