Оптимизация 3 красноречивых запросов (продолжительность запроса 1,5 секунды…)

#php #mysql #laravel #laravel-query-builder

Вопрос:

Я хотел бы оптимизировать эти 3 запроса, которые позволяют мне отображать пользователей, разместивших наибольшее количество фотографий на нашем сайте, в соответствии с критериями даты (текущая неделя, прошлая неделя, с начала).

Время запроса измеряется в 1,5 секунды с помощью панели отладки, это действительно долго ! Знаете ли вы, как его оптимизировать ?

 public function show() {  $currentWeek = User::whereHas('pictures')  -gt;whereHas('pictures', fn ($q) =gt; $q-gt;whereBetween('created_at', [Carbon::now()-gt;startOfWeek(), Carbon::now()-gt;endOfWeek()]))  -gt;withCount(['pictures' =gt; fn ($q) =gt; $q-gt;whereBetween('created_at', [Carbon::now()-gt;startOfWeek(), Carbon::now()-gt;endOfWeek()])])  -gt;orderBy('pictures_count', 'DESC')  -gt;limit(10)  -gt;get();   $lastWeek = User::whereHas('pictures')  -gt;whereHas('pictures', fn ($q) =gt; $q-gt;whereBetween('created_at', [Carbon::now()-gt;startOfWeek()-gt;subWeek(), Carbon::now()-gt;endOfWeek()-gt;subWeek()]))  -gt;withCount(['pictures' =gt; fn ($q) =gt; $q-gt;whereBetween('created_at', [Carbon::now()-gt;startOfWeek()-gt;subWeek(), Carbon::now()-gt;endOfWeek()-gt;subWeek()])])  -gt;orderBy('pictures_count', 'DESC')  -gt;limit(10)  -gt;get();   $overall = User::whereHas('pictures')  -gt;whereHas('pictures')  -gt;withCount('pictures')  -gt;orderBy('pictures_count', 'DESC')  -gt;limit(10)  -gt;get();   return view('users.leaderboard', [  'currentWeek' =gt; $currentWeek,  'lastWeek' =gt; $lastWeek,  'overall' =gt; $overall,  ]); }  

Комментарии:

1. вы пробовали создать index столбец базы данных «created_at»?

2. нет, никогда, не могли бы вы объяснить подробнее ? 🙂

3. да, конечно. индексы точно так же, как индексы перечислены в начале любой книги, чтобы найти элементы по их названиям на определенных страницах. База данных использует индексы для того же. например, у вас есть таблица, содержащая 1 000 000 данных, и вы запрашиваете поиск по имени «xyz». Таким образом, в обычном случае база данных будет искать » xyz «от начала до конца и будет просматривать все 1,00,000 записей, чтобы найти «xyz». Но если вы создадите индекс в столбце «имя», то база данных будет искать только там, где имя начинается с «x», и ей не нужно будет просматривать все 1 000 000 записей, и, таким образом, результат запроса будет намного быстрее

4. да, для доступа к большему количеству данных требуется больше времени

5. Хорошо, спасибо за вашу драгоценную помощь и за ваше время 😉

Ответ №1:

Во-первых, во-первых, вы дважды звонили whereHas по поводу отношений с фотографиями, так что вы можете избавиться от неквалифицированного звонка.

 $currentWeek = User::whereHas('pictures', fn ($q) =gt; $q-gt;whereBetween('created_at', [now()-gt;startOfWeek(), now()-gt;endOfWeek()]))  -gt;withCount(['pictures' =gt; fn ($q) =gt; $q-gt;whereBetween('created_at', [now()-gt;startOfWeek(), now()-gt;endOfWeek()])])  -gt;orderBy('pictures_count', 'DESC')  -gt;limit(10)  -gt;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 с закрытием, поэтому вы учитываете только фотографии в диапазоне дат. Что произойдет, если условие не совпадает? Он возвращает ноль. Поскольку вы все равно выполняете обратную сортировку по количеству, другой вызов также whereHas может быть отправлен.

 $currentWeek = User::withCount(['pictures' =gt; fn ($q) =gt; $q-gt;whereBetween('created_at', [now()-gt;startOfWeek(), now()-gt;endOfWeek()])])  -gt;orderBy('pictures_count', 'DESC')  -gt;limit(10)  -gt;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 элементов, даже если некоторые из них равны нулю. Если вы не хотите, чтобы нули были в вашей таблице лидеров, просто отфильтруйте их из коллекции.

Комментарии:

1. Большое вам спасибо за этот ответ! Я буду применять все это. Я буду применять все это. Спасибо!