【问题标题】:Laravel - N+1 problem within my relationship recursionLaravel - 我的关系递归中的 N+1 问题
【发布时间】:2020-06-28 22:48:24
【问题描述】:

我正在构建一个 laravel 网站。它有提交,提交有 cmets。我想急切地加载这些 cmets 及其子 cmets(以及那些评论的子代等)。但只是在一定程度上。

子评论循环仅在 $loop->depth 10 和 $loop->iteration 8 内加载子 cmets。我不想急切加载我什至不会显示的 cmets。

这是我目前所拥有的:

控制器:

$repliesCount = Comment::with([
            'owner',
            'savedComments',
            'votes',
        ])
        ->where('submission_id', $submission->id)
        ->whereNotNull('parent_id')
        ->count();


$comments = Comment::where('submission_id', $submission->id)
    ->whereNull('parent_id')
    ->with([
        'owner',
        'savedComments',
        'votes',
        'submission',
        'reports'
    ])
    ->orderBy('removed', 'asc')
    ->orderBy($sortBy, $direction)
    ->paginate(100);

$replies = Comment::where('submission_id', $submission->id)
            ->whereNotNull('parent_id')
            ->with([
                'owner',
                'savedComments',
                'votes',
                'submission',
                'reports'
            ])
            ->orderBy('removed', 'asc')
            ->orderBy($sortBy, $direction)
            ->paginate($repliesCount);
            
$comments_by_id = new Collection();
$replies_by_id = new Collection();
foreach ($comments as $comment) {
    $comments_by_id->put($comment->id, $comment);
    $comments_by_id->get($comment->id)->children = new Collection();
}
foreach ($replies as $reply) {
    $replies_by_id->put($reply->id, $reply);
    $replies_by_id->get($reply->id)->children = new Collection();
}
foreach ($replies as $key => $reply) {
    if ($comments_by_id->get($reply->parent_id)) {
        $comments_by_id->get($reply->parent_id)->children->push($reply);
    } elseif ($replies_by_id->get($reply->parent_id)) {
        $replies_by_id->get($reply->parent_id)->children->push($reply);
    }
}

刀片:

@foreach ($comment->children as $comment)
    @if ($loop->depth == 10)
        <div>
            <a href="{{ route('get.submission', ['subchan' => $comment->submission->subchan, 'id' => $comment->submission->id, 'URLtitle' => $comment->submission->URL_title,'commentID' => $comment->parent_id]) }}">Continue this thread</a>
        </div>
        @break
    @endif
    <div class="comment-container comment-container-child" id="comment-container-{{$comment->id}}" hidden-level="{{ceil(($loop->iteration + 3) / 10) - 1}}">
        @include('partials.comment_block')
    </div>
    @if ($loop->iteration % 10 == 7 && $loop -> remaining > 0)
        
        <div class="loadMoreReplies comment-container-child load-more"
        hidden-level="{{ceil(($loop->iteration + 3) / 10) - 1}}"
        data-submission-id="{{ $comment->submission->id }}"
        data-parent-id="{{$comment->parent_id}}"
        
        >Load More Replies (<span id="remaining-reply-count-{{$comment->parent_id}}">{{ $loop->remaining }}</span>)</div>
    @endif
@endforeach

基本上,我需要 100 cmets ($comments),并且我渴望加载我需要的关系(例如“所有者”关系)。

然后我进行查询以获取该提交 ($replies) 及其所有关系的所有子 cmets。

之后,我创建了一个集合,将每个子回复推送到其父 comment-&gt;children

这行得通。但是,它会加载给定评论的所有儿童回复。因此,即使我只加载 100 个父 cmets,如果其中一个 cmets 或子 cmets 有 THOUSANDS 子 cmets,它也会一次性加载所有这些。

这里有一张图片可以更好地说明(5000 条回复被隐藏但仍在被查询,因此页面加载需要很长时间):

我最终想要的是它只渴望加载并返回最多 10 个子 cmets,同时还传递评论有多少孩子,以便我可以显示缺少多少孩子。

【问题讨论】:

    标签: php laravel


    【解决方案1】:

    有点复杂。您需要两个孩子的关系,一个只获得 10 条记录,另一个获得全部(默认情况下)。所以我假设您渴望通过 $with 属性(如$with = ['children'];)加载模型中的所有 cmets。相反,您可以在评论模型中执行此操作:

    class Comment extends Model
    {
        protected $with = ['tenChildren'];
    
        public function tenChildren()
        {
            // fill others params if needed
            // sort it or whatever
            return $this->hasMany(Comment::class)->limit(10);
        }
    
        public function children()
        {
            return $this->hasMany(Comment::class);// fill others params if needed
        }
    }
    

    所以第一次只有有 10 个孩子的父母收到。那么你只需要从后端获取剩余的孩子就可以了,你有一点问题。使用具有状态(检测接收了多少孩子)的 js 现代框架使其变得容易。但要获得额外的孩子,只需要一条路线来让孩子进入分页数据。例如,要让其他孩子发表第二条评论,您需要将 get req 发送到此添加 /api/comments/2?page=2

    【讨论】:

    • 我不在模型中做子关系,因为没有办法在递归中急切地加载嵌套关系。如果您只能回复父 cmets,这将不是问题。然而,Laravel 不允许你急切加载孩子的孩子,所以每个人都做它自己的数据库查询(N+1 问题)。
    • 你可以用这种方式做到这一点,甚至可以回复所有只管理状态事务的 cmets。还有第二种方法可以急切地加载孩子的孩子laracasts.com/discuss/channels/eloquent/… 这个家伙是数据库的佼佼者,尤其是在 laravel 社区中。
    • 我很久以前就尝试过类似的方法。它对我不起作用。您可以像这样急切地加载嵌套关系:->with(["children.children.children...."]),但是每次向下一层深度时,您都需要添加更多。这不是一个好的解决方案。
    猜你喜欢
    • 2019-04-15
    • 2017-12-22
    • 1970-01-01
    • 2014-05-02
    • 1970-01-01
    • 1970-01-01
    • 2011-03-14
    • 2020-02-14
    • 2015-08-29
    相关资源
    最近更新 更多