Подтвердить что ты не робот

MySQL "IN" запрашивает ужасно медленно с подзапросом, но быстро с явными значениями

У меня есть запрос MySQL (Ubu 10.04, Innodb, Core i7, 16Gb RAM, SSD-диски, оптимизированные параметры MySQL):

SELECT
COUNT(DISTINCT subscriberid)
FROM
em_link_data
WHERE
linkid in (SELECT l.id FROM em_link l WHERE l.campaignid = '2900' AND l.link != 'open')

В таблице em_link_data имеется около 7 миллионов строк, у em_link - несколько тысяч. Этот запрос займет около 18 секунд. Однако, если подставить результаты подзапроса и выполните следующее:

SELECT
COUNT(DISTINCT subscriberid)
FROM
em_link_data
WHERE
linkid in (24899,24900,24901,24902);

тогда запрос будет выполняться менее чем за 1 миллисекунду. Только в подзапросе работает менее 1 мс, индексируется индекс linkid.

Если я переписал запрос как соединение, то и меньше 1 мс. Почему запрос "IN" настолько медленный с подзапросом в нем и почему так быстро со значениями в нем? Я не могу переписать запрос (купленное программное обеспечение), поэтому я надеялся, что есть некоторые подсказки или подсказки, чтобы ускорить этот запрос! Любая помощь приветствуется.

4b9b3361

Ответ 1

Подзапросы выполняются каждый раз, когда вы их оцениваете (в любом случае в MySQL, а не во всех RDBMS), т.е. вы в основном запускаете 7 миллионов запросов! Использование JOIN, если возможно, уменьшит это до 1. Даже если добавление индексирования улучшает производительность тех, вы все еще выполняете их.

Ответ 2

Да, IN с подзапросами медленно. Вместо этого используйте соединение.

SELECT
COUNT(DISTINCT subscriberid)
FROM em_link_data JOIN em_link ON em_link_data.linkid=em_link.id
WHERE em_link.campaignid = '2900' AND em_link.link != 'open'

И убедитесь, что вы указали индексы на em_link_data.linkid и em_link.id.

Ответ 3

Проблема заключается в том, что MySQL выполняет запросы извне внутрь, в то время как вы можете подумать, что ваш подзапрос выполняется один раз, а затем его результаты передаются выражению WHERE внешнего запроса (см. Документация по MySQL).

Если вы не можете переписать свой запрос, вы должны сделать следующие оптимизации:

  • добавьте индекс на campaignid и link, так как FrustratedWithFormsDesigner сказал
  • убедитесь, что подзапрос правильно использует индексы, выполнив EXPLAIN SELECT ...
  • включить и настроить кеш запросов, поскольку это должно ускорить подзапрос, вызываемый несколько раз

Еще одна идея - установить прокси-сервер MySQL и написать немного script, который перехватывает ваш запрос и перезаписывает его для использования соединения.

Ответ 4

Если ваш подзапрос работает быстро, значит, кампания и ссылка абсолютно индексируются. Таким образом, l.id является PK и кластеризуется быстро. Но, насколько я помню (с последнего раза я проверил этот вопрос), mysql описывает свои внутренние оптимизации для подзапросов "in", чтобы использовать индексный результат подзапроса для повышения производительности, а также использует кеш для левой стороны "IN" , чтобы перетащить его внутри подзапроса быстрее, и если индексы установлены верно, он не должен иметь такой разницы, чтобы использовать внутреннее соединение или "IN" , а не кеширование, и это может быть связано с проблемой кеша и массивными данными. http://dev.mysql.com/doc/internals/en/transformation-scalar-in.html

Я не знаю ситуации с программным обеспечением, но если вы можете использовать INNER JOIN, и у вас есть (возможно) некоторые дополнительные определения перед предложением IN в предложении WHERE вашего внешнего запроса, обязательно переместите эти предложения до ваш основной INNER JOIN через временный INNER JOIN ведет себя подобно последовательному "where" предложению последовательно и уменьшает количество перекрестных сравнений в JOIN следующим образом:

SELECT ... FROM t
INNER JOIN (SELECT 1) AS tmp ON t.asd=23
INNER JOIN t2 ON ...

Примеры сравнения нормального и временного поиска: 1000 * 1000 > 1000 + (100 * 1000)

Также кажется, что подзапрос фильтруется постоянными vals, поэтому, если бы это был я, я собирался помещать предложения в подзапрос, генерирующий набор результатов, и уменьшать количество сравнений в JOIN следующим образом:

SELECT ... FROM t
INNER JOIN (SELECT ... FROM t2 WHERE constant clauses) AS tbl2 ON ...

В любом случае, в запросе "IN" сравнение любого столбца таблицы в подзапросе с любым столбцом таблицы во внешнем запросе требует, чтобы столбцы обеих сторон были точно проиндексированы (в отношении составных индексов), но все же это может проблема с кешем.

EDITED: Также мне было любопытно спросить: может ли сделать составной индекс на l.campaignid, l.link и l.id какой-нибудь смысл?