优化 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 个项目,即使其中一些项目为零。如果您不想在排行榜中出现零,只需将它们从集合中过滤掉即可。
我想优化这 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 个项目,即使其中一些项目为零。如果您不想在排行榜中出现零,只需将它们从集合中过滤掉即可。