Советы по улучшению этого медленного запроса mysql?
Я использую запрос, который обычно выполняется менее чем за секунду, но иногда для завершения требуется от 10 до 40 секунд. На самом деле я не совсем понимаю, как работает подзапрос, я просто знаю, что он работает, поскольку он дает мне 15 строк для каждого faverprofileid.
Я регистрирую медленные запросы, и он сообщает мне, что было проверено 5823244 строки, что странно, потому что ни в одной из задействованных таблиц нет и близко к такому количеству строк (в таблице избранного больше всего - 50 000 строк).
Кто-нибудь может предложить мне несколько указателей? Это проблема с подзапросом и необходимость использования сортировки файлов?
РЕДАКТИРОВАТЬ: Запуск объяснения показывает, что таблица пользователей не использует индекс (хотя id является первичным ключом). Под дополнительным сказано: Использование временного; Использование filesort.
SELECT F.id,F.created,U.username,U.fullname,U.id,I.*
FROM favorites AS F
INNER JOIN users AS U ON F.faver_profile_id = U.id
INNER JOIN items AS I ON F.notice_id = I.id
WHERE faver_profile_id IN (360,379,95,315,278,1)
AND F.removed = 0
AND I.removed = 0
AND F.collection_id is null
AND I.nudity = 0
AND (SELECT COUNT(*) FROM favorites WHERE faver_profile_id = F.faver_profile_id
AND created > F.created AND removed = 0 AND collection_id is null) < 15
ORDER BY F.faver_profile_id, F.created DESC;
Ответов (5)5
Думаю с GROUP BY
и HAVING
быстрее должно быть. Это то, что вы хотите?
SELECT F.id,F.created,U.username,U.fullname,U.id, I.field1, I.field2, count(*) as CNT
FROM favorites AS F
INNER JOIN users AS U ON F.faver_profile_id = U.id
INNER JOIN items AS I ON F.notice_id = I.id
WHERE faver_profile_id IN (360,379,95,315,278,1)
AND F.removed = 0
AND I.removed = 0
AND F.collection_id is null
AND I.nudity = 0
GROUP BY F.id,F.created,U.username,U.fullname,U.id,I.field1, I.field2
HAVING CNT < 15
ORDER BY F.faver_profile_id, F.created DESC;
Не знаю, какие поля items
вам нужны, поэтому я поставил заполнители.
Вы можете сделать цикл для каждого идентификатора и использовать limit вместо подзапроса count (*):
foreach $id in [123,456,789]:
SELECT
F.id,
F.created,
U.username,
U.fullname,
U.id,
I.*
FROM
favorites AS F INNER JOIN
users AS U ON F.faver_profile_id = U.id INNER JOIN
items AS I ON F.notice_id = I.id
WHERE
F.faver_profile_id = {$id} AND
I.removed = 0 AND
I.nudity = 0 AND
F.removed = 0 AND
F.collection_id is null
ORDER BY
F.faver_profile_id,
F.created DESC
LIMIT
15;
Я предполагаю, что результат этого запроса будет отображаться в виде страничного списка. В этом случае, возможно, вы могли бы подумать о том, чтобы сделать более простой «несвязанный запрос» и выполнить второй запрос для каждой строки, чтобы прочитать только показанные 15, 20 или 30 элементов. Разве JOIN не было тяжелой операцией? Это упростило бы запрос и не стало бы медленнее при увеличении объединенных таблиц.
Подскажите, пожалуйста, если я ошибаюсь.
Количество проверенных строк велико, поскольку многие строки проверялись более одного раза. Вы получаете это из-за неправильно оптимизированного плана запроса, который приводит к сканированию таблицы, когда должен был выполняться поиск по индексу. В этом случае количество проверяемых строк является экспоненциальным, то есть по порядку величины сравнимо с произведением общего количества строк в более чем одной таблице.
- Убедитесь, что вы запустили ANALYZE TABLE для своих трех таблиц.
- Прочтите, как избежать сканирования таблиц и определить, а затем создать недостающие индексы.
- Повторите АНАЛИЗ и еще раз объясните свои запросы
- количество проверяемых строк должно резко сократиться
- если нет, опубликуйте полный план объяснения
- используйте подсказки запроса, чтобы принудительно использовать индексы (чтобы увидеть имена индексов для таблицы, используйте SHOW INDEX ):
SELECT F.id,F.created,U.username,U.fullname,U.id,I.*
FROM favorites AS F
FORCE INDEX (faver_profile_id_key)
INNER JOIN users AS U
FORCE INDEX FOR JOIN (PRIMARY)
ON F.faver_profile_id = U.id
INNER JOIN items AS I
FORCE INDEX FOR JOIN (PRIMARY)
ON F.notice_id = I.id
WHERE faver_profile_id IN (360,379,95,315,278,1)
AND F.removed = 0
AND I.removed = 0
AND F.collection_id is null
AND I.nudity = 0
AND (SELECT COUNT(*) FROM favorites
FORCE INDEX (faver_profile_id_key)
WHERE faver_profile_id = F.faver_profile_id
AND created > F.created AND removed = 0 AND collection_id is null) < 15
ORDER BY F.faver_profile_id, F.created DESC;
Вы также можете изменить свой запрос, чтобы использовать GROUP BY faver_profile_id
/ HAVING count > 15
вместо вложенного SELECT COUNT(*)
подзапроса, как предлагает vartec
. Производительность как исходного vartec
запроса, так и запроса должна быть сопоставимой, если оба они оптимизированы должным образом, например, с помощью подсказок (в вашем запросе будет использоваться поиск по вложенному индексу, а в vartec
запросе - стратегия на основе хешей).
Я предлагаю вам использовать Mysql Explain Query, чтобы увидеть, как ваш сервер mysql обрабатывает запрос. Держу пари, что ваши показатели не оптимальны, но объяснение должно работать намного лучше, чем моя ставка.