Как оптимизировать запрос для поиска строк, в которых строки условного соединения не существуют?

У меня есть таблица ключевых слов, которые я регулярно обновляю с помощью API удаленного поиска, и еще одна таблица, в которой каждый раз при обновлении одного из ключевых слов появляется строка. Я использую эту таблицу, чтобы запретить нескольким процессам наступать друг на друга и обновлять одно и то же ключевое слово, а также собирать статистику. Поэтому, когда я запускаю свою программу, она запрашивает все ключевые слова, которые не имеют запроса в данный момент в обработке и не имеют успешного запроса в течение последних 15 минут или любого другого интервала. Некоторое время все работало нормально, но теперь в таблице keywords_requests почти 2 миллиона строк, и все сильно тормозит. У меня есть индексы почти для каждого столбца в таблице keywords_requests, но безрезультатно.

Я регистрирую медленные запросы, а этот, как видите, занимает целую вечность. Что я могу сделать?

# Query_time: 20 Lock_time: 0 Rows_sent: 568 Rows_examined: 1826718

SELECT Keyword.id, Keyword.keyword
FROM `keywords` as Keyword
LEFT JOIN `keywords_requests` as KeywordsRequest
ON (
  KeywordsRequest.keyword_id = Keyword.id
  AND (KeywordsRequest.status = 'success' OR KeywordsRequest.status = 'active')
  AND KeywordsRequest.source_id = '29'
  AND KeywordsRequest.created > FROM_UNIXTIME(1234551323)
)
WHERE KeywordsRequest.id IS NULL
GROUP BY Keyword.id
ORDER BY KeywordsRequest.created ASC;

person Community    schedule 13.02.2009    source источник


Ответы (4)


Кажется, ваш самый избирательный индекс на Keywords — это индекс на KeywordRequest.created.

Попробуйте переписать запрос таким образом:

SELECT Keyword.id, Keyword.keyword
FROM `keywords` as Keyword
LEFT OUTER JOIN (
  SELECT *
  FROM `keywords_requests` as kr
  WHERE created > FROM_UNIXTIME(1234567890) /* Happy unix_time! */
) AS KeywordsRequest
ON (
  KeywordsRequest.keyword_id = Keyword.id
  AND (KeywordsRequest.status = 'success' OR KeywordsRequest.status = 'active')
  AND KeywordsRequest.source_id = '29'
)
WHERE keyword_id IS NULL;

Это (надеюсь) объединит два не очень больших источника.

И Билл Карвин прав, вам не нужны GROUP BY или ORDER BY

В MySQL нет тонкого контроля над планами, но вы можете попробовать (попробовать) улучшить свой запрос следующими способами:

  1. Создайте составной индекс на (keyword_id, status, source_id, created) и сделайте так:

    SELECT Keyword.id, Keyword.keyword
    FROM `keywords` as Keyword
    LEFT OUTER JOIN `keywords_requests` kr
    ON (
      keyword_id = id
      AND status = 'success'
      AND source_id = '29'
      AND created > FROM_UNIXTIME(1234567890)
    )
    WHERE keyword_id IS NULL
    UNION
    SELECT Keyword.id, Keyword.keyword
    FROM `keywords` as Keyword
    LEFT OUTER JOIN `keywords_requests` kr
    ON (
      keyword_id = id
      AND status = 'active'
      AND source_id = '29'
      AND created > FROM_UNIXTIME(1234567890)
    )
    WHERE keyword_id IS NULL
    

    В идеале это должно использовать NESTED LOOPS в вашем индексе.

  2. Создайте составной индекс на (status, source_id, created) и сделайте так:

    SELECT Keyword.id, Keyword.keyword
    FROM `keywords` as Keyword
    LEFT OUTER JOIN (
      SELECT *
      FROM `keywords_requests` kr
      WHERE
        status = 'success'
        AND source_id = '29'
        AND created > FROM_UNIXTIME(1234567890)
      UNION ALL
      SELECT *
      FROM `keywords_requests` kr
      WHERE
        status = 'active'
        AND source_id = '29'
        AND created > FROM_UNIXTIME(1234567890)
    )
    ON keyword_id = id
    WHERE keyword_id IS NULL
    

    Мы надеемся, что это будет использовать HASH JOIN в еще более ограниченной хэш-таблице.

person Quassnoi    schedule 13.02.2009
comment
Ничего себе, это сократило время запроса до нуля в нескольких случаях и менее секунды в общем случае. Очень хорошо. Последующий вопрос: чем дальше я смотрю, тем дольше время запроса, поэтому могу ли я оптимизировать его с помощью многостолбцового индекса? - person ; 14.02.2009

При диагностике производительности запросов MySQL в первую очередь необходимо проанализировать отчет из ОБЪЯСНИТЬ.

Если вы научитесь читать информацию, которую дает вам EXPLAIN, вы сможете увидеть, где запросы не могут использовать индексы, или где они вызывают дорогостоящую сортировку файлов или другие красные флажки производительности.

Я заметил, что в вашем запросе GROUP BY не имеет значения, поскольку из KeywordRequests будет возвращена только одна строка NULL. Также ORDER BY не имеет значения, так как вы упорядочиваете столбец, который всегда будет NULL из-за вашего предложения WHERE. Если вы удалите эти пункты, вы, вероятно, устраните файловую сортировку.

Также рассмотрите возможность перезаписи запроса в другие формы и измерьте производительность каждой из них. Например:

SELECT k.id, k.keyword
FROM `keywords` AS k
WHERE NOT EXISTS (
  SELECT * FROM `keywords_requests` AS kr
  WHERE kr.keyword_id = k.id
   AND kr.status IN ('success', 'active')
   AND kr.source_id = '29'
   AND kr.created > FROM_UNIXTIME(1234551323)
);

Другие советы:

  • Является ли kr.source_id целым числом? Если да, сравните с целым числом 29 вместо строки '29'.
  • Есть ли соответствующие индексы на keyword_id, status, source_id, created? Возможно, лучше всего будет даже составной индекс по всем четырем столбцам, поскольку MySQL будет использовать только один индекс для каждой таблицы в заданном запросе.

Вы сделали скриншот своего вывода EXPLAIN и разместили ссылку в комментариях. Я вижу, что запрос не использует индекс из ключевых слов, что имеет смысл, поскольку вы все равно сканируете каждую строку в этой таблице. Фраза «Не существует» указывает на то, что MySQL немного оптимизировал LEFT OUTER JOIN.

Я думаю, что это должно быть улучшено по сравнению с вашим исходным запросом. GROUP BY/ORDER BY, вероятно, заставляла его сохранять промежуточный набор данных в виде временной таблицы и сортировать его на диске (что очень медленно!). То, что вы ищете, это «Использование временного; использование файловой сортировки» в столбце «Дополнительно» информации EXPLAIN.

Таким образом, вы, возможно, уже достаточно улучшили его, чтобы устранить узкое место на данный момент.

Я заметил, что возможные ключи, вероятно, указывают на то, что у вас есть индивидуальные индексы для четырех столбцов. Вы можете улучшить это, создав составной индекс:

CREATE INDEX kr_cover ON keywords_requests
  (keyword_id, created, source_id, status);

Вы можете дать MySQL подсказку для использования определенного показатель:

... FROM `keywords_requests` AS kr USE INDEX (kr_cover) WHERE ...
person Bill Karwin    schedule 13.02.2009
comment
Вау, спасибо за вашу помощь. EXPLAIN для этого запроса не очень помог мне. Вот что я получил после удаления предложений GROUP BY и ORDER BY... что я ищу? img.skitch.com/20090213-fq5mkucasgdqc3ck9aeca6qr9p.jpg - person ; 14.02.2009
comment
Кроме того, я получил наибольшее улучшение скорости, используя приведенный ниже пример Quassnoi. Но если бы я хотел еще больше ускорить процесс, мог бы мне помочь многоколоночный индекс? Хотел бы я просто указать source_id, status и keyword_id в индексе, поскольку created находится в подзапросе? - person ; 14.02.2009
comment
Трудно предположить, поскольку выбор индексов оптимизатором частично зависит от распределения значений данных в вашей базе данных. Лучший совет — попробовать несколько разных способов и измерить как информацию EXPLAIN, так и фактическое время выполнения запроса. - person Bill Karwin; 14.02.2009

Не знаю насчет MySQL, но в MSSQL я бы выбрал следующие линии атаки:

1) Создайте покрывающий индекс для статуса KeywordsRequest, source_id и created.

2) ОБЪЕДИНИТЕ результаты, объединенные оператором ИЛИ, в KeywordsRequest.status.

3) Используйте NOT EXISTS вместо внешнего соединения (и попробуйте использовать UNION вместо OR)

person Kristen    schedule 13.02.2009

Попробуйте это SELECT Keyword.id, Keyword.keyword FROM keywords as Keyword LEFT JOIN (выберите * из keywords_requests, где source_id = '29' и (status = 'success' OR status = 'active') AND source_id = '29' AND created > FROM_UNIXTIME(1234551323) AND id IS NULL ) as KeywordsRequest ON ( KeywordsRequest.keyword_id = Keyword.id

) СГРУППИРОВАТЬ ПО Keyword.id ORDER BY KeywordsRequest.created ASC;

person Community    schedule 04.08.2009