Советы по улучшению этого медленного запроса 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)

Решение

Думаю с 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 обрабатывает запрос. Держу пари, что ваши показатели не оптимальны, но объяснение должно работать намного лучше, чем моя ставка.