优化 3 Eloquent 个请求(请求持续时间为 1.5 秒...)

Optimization of 3 Eloquent requests (1.5 seconds of request duration...)

我想优化这 3 个查询,使我能够根据日期标准(本周、上周、自开始以来)显示在我们网站上发布照片最多的用户。

调试栏测得查询时间为1.5秒,真长! 你知道怎么优化吗?

public function show()
{
    $currentWeek = User::whereHas('pictures')
        ->whereHas('pictures', fn ($q) => $q->whereBetween('created_at', [Carbon::now()->startOfWeek(), Carbon::now()->endOfWeek()]))
        ->withCount(['pictures' => fn ($q) => $q->whereBetween('created_at', [Carbon::now()->startOfWeek(), Carbon::now()->endOfWeek()])])
        ->orderBy('pictures_count', 'DESC')
        ->limit(10)
        ->get();

    $lastWeek = User::whereHas('pictures')
        ->whereHas('pictures', fn ($q) => $q->whereBetween('created_at', [Carbon::now()->startOfWeek()->subWeek(), Carbon::now()->endOfWeek()->subWeek()]))
        ->withCount(['pictures' => fn ($q) => $q->whereBetween('created_at', [Carbon::now()->startOfWeek()->subWeek(), Carbon::now()->endOfWeek()->subWeek()])])
        ->orderBy('pictures_count', 'DESC')
        ->limit(10)
        ->get();

    $overall = User::whereHas('pictures')
        ->whereHas('pictures')
        ->withCount('pictures')
        ->orderBy('pictures_count', 'DESC')
        ->limit(10)
        ->get();

    return view('users.leaderboard', [
        'currentWeek' => $currentWeek,
        'lastWeek' => $lastWeek,
        'overall' => $overall,
    ]);
}

首先,你已经在图片关系上调用了两次whereHas,所以你可以去掉不合格的调用。

$currentWeek = User::whereHas('pictures', fn ($q) => $q->whereBetween('created_at', [now()->startOfWeek(), now()->endOfWeek()]))
    ->withCount(['pictures' => fn ($q) => $q->whereBetween('created_at', [now()->startOfWeek(), now()->endOfWeek()])])
    ->orderBy('pictures_count', 'DESC')
    ->limit(10)
    ->get();

这减少了 SQL 查询:

select `users`.*, (
    select count(*) from `pictures` where `users`.`id` = `pictures`.`user_id` and `created_at` between ? and ? and `pictures`.`deleted_at` is null
) as `pictures_count`
from `users`
where exists (select * from `pictures` where `users`.`id` = `pictures`.`user_id` and `pictures`.`deleted_at` is null)
    and exists (select * from `pictures` where `users`.`id` = `pictures`.`user_id` and `created_at` between ? and ? and `pictures`.`deleted_at` is null)
    and `users`.`deleted_at` is null
    order by `pictures_count` desc
    limit 10

为此:

select `users`.*, (
    select count(*) from `pictures` where `users`.`id` = `pictures`.`user_id` and `created_at` between ? and ? and `pictures`.`deleted_at` is null
) as `pictures_count`
from `users`
where exists (select * from `pictures` where `users`.`id` = `pictures`.`user_id` and `created_at` between ? and ? and `pictures`.`deleted_at` is null)
    -- no second where exists clause
    and `users`.`deleted_at` is null
    order by `pictures_count` desc
    limit 10

现在,您在 where 子句中只有一个条件。它选择在指定日期范围内拥有图片的用户。看起来更好,对吧?


但是,您已经在使用带有闭包的 withCount,因此您只计算日期范围内的图片。如果条件不匹配会发生什么?它 returns 为零。由于您无论如何都是按计数反向排序的,因此对 whereHas 的另一个调用也可以进行。

$currentWeek = User::withCount(['pictures' => fn ($q) => $q->whereBetween('created_at', [now()->startOfWeek(), now()->endOfWeek()])])
    ->orderBy('pictures_count', 'DESC')
    ->limit(10)
    ->get();

现在你的 SQL 看起来像这样:

select `users`.*, (
    select count(*) from `pictures` where `users`.`id` = `pictures`.`user_id` and `created_at` between ? and ? and `pictures`.`deleted_at` is null
) as `pictures_count`
    from `users`
    where `users`.`deleted_at` is null
    -- no where exists clauses at all any more
    order by `pictures_count` desc
    limit 10

它应该 运行 快得多。这确实会稍微改变您的数据;生成的集合将始终包含 10 个项目,即使其中一些项目为零。如果您不想在排行榜中出现零,只需将它们从集合中过滤掉即可。