嵌套关系和自定义访问器的预加载会导致性能问题

问题描述 投票:0回答:1

我正在使用 Laravel 10 开发一个应用程序,并且面临与急切加载嵌套关系和自定义访问器相关的性能问题。

场景: 我有三个模型:用户、帖子和评论。

一个用户有很多帖子。 一个帖子有很多评论。 我在 Post 模型上设置了一个自定义访问器来计算评论数。

以下是我的模型的相关部分:

// User.php
class User extends Model
{
    public function posts()
    {
        return $this->hasMany(Post::class);
    }
}

// Post.php
class Post extends Model
{
    protected $appends = ['comment_count'];

    public function comments()
    {
        return $this->hasMany(Comment::class);
    }

    public function getCommentCountAttribute()
    {
        return $this->comments()->count();
    }
}

// Comment.php
class Comment extends Model
{
    public function post()
    {
        return $this->belongsTo(Post::class);
    }
}

问题: 在我的控制器中,我试图加载所有用户的帖子以及每个帖子的评论数量。我是这样做的:

$users = User::with(['posts.comments'])->get();

我面临的问题是,由于 N+1 查询问题,这种方法导致性能显着下降。为每个帖子调用自定义访问器 getCommentCountAttribute,这会导致为每个帖子的评论计数执行一个新查询。

我尝试过的:

  1. 添加 withCount:我尝试通过在控制器中使用 withCount 来优化它,如下所示:
$users = User::with(['posts' => function($query) {
    $query->withCount('comments');
}])->get();

这可行,但问题是它不使用自定义访问器,因此未填充 Post 模型中的 comment_count 属性。

  1. 使用 loadMissing:我尝试使用 loadMissing 来避免加载已加载的关系:
$users->loadMissing('posts.comments');

但这并不能解决性能问题,因为它仍然会导致过多的查询。

  1. 避免自定义访问器:我考虑过完全避免自定义访问器,但该访问器在应用程序中的多个位置使用,重构它会很麻烦。

有没有一种方法可以继续使用自定义访问器,同时避免 N+1 查询问题?如何优化这种急切加载以减少查询数量,同时仍然有效地获取每个帖子的 comment_count ?任何见解或替代方法将不胜感激!

php laravel performance eager-loading accessor
1个回答
0
投票

将访问器更改为:

public function getCommentCountAttribute()
{
  return $this->comments->count();
}

这具有在模型上加载

comments
关系的效果(如果尚未加载)。由于您使用的是 User::with(['posts.comments']);
,因此帖子将已加载 
comments
 关系。


或者,将访问器更改为

getCommentsCountAttribute

,以便可以用 
comments_count
 调用它(注意复数)。 Laravel 自动使用关系名称来设置 count 属性。关系是
comments

© www.soinside.com 2019 - 2024. All rights reserved.