MySQL «IN» запросы ужасно медленно с подзапросом, но быстро с явными значениями - PullRequest
20 голосов
/ 16 февраля 2011

У меня запрос MySQL (Ubu 10.04, Innodb, Core i7, 16 ГБ ОЗУ, 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 мс, индекс столбца индексируется.

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

Ответы [ 4 ]

23 голосов
/ 16 февраля 2011

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

4 голосов
/ 16 февраля 2011

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

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

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

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

4 голосов
/ 16 февраля 2011

Да, 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.

0 голосов
/ 24 января 2016

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

Я не знаю ситуацию с программным обеспечением, но если вы можете использовать INNER JOIN и у вас есть (возможно) некоторые дополнительные определения перед предложением IN в предложении WHERE вашего внешнего запроса, убедитесь, чтопереместить эти пункты в before вашего основного 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)

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

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

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

РЕДАКТИРОВАНИЕ: Также мне было любопытно спросить: может ли иметь смысл создание составного индекса для l.campaignid, l.link и l.id?

...