MySQL не использует индексы с предложением WHERE IN? - PullRequest
48 голосов
/ 25 февраля 2009

Я пытаюсь оптимизировать некоторые запросы к базе данных в моем приложении Rails, и у меня есть несколько, которые поставили меня в тупик. Все они используют IN в предложении WHERE и все выполняют полное сканирование таблицы, хотя соответствующий индекс, кажется, на месте.

Например:

SELECT `user_metrics`.* FROM `user_metrics` WHERE (`user_metrics`.user_id IN (N,N,N,N,N,N,N,N,N,N,N,N))

выполняет полное сканирование таблицы, а EXPLAIN говорит:

select_type: simple
type: all
extra: using where
possible_keys: index_user_metrics_on_user_id  (which is an index on the user_id column)
key: (none)
key_length: (none)
ref: (none)
rows: 208

Не используются ли индексы, когда используется оператор IN, или мне нужно сделать что-то по-другому? Запросы здесь генерируются Rails, чтобы я мог вернуться к определению моих отношений, но я подумал, что сначала я начну с потенциальных исправлений на уровне БД.

Ответы [ 5 ]

44 голосов
/ 25 февраля 2009

См. Как MySQL использует индексы .

Также проверьте, выполняет ли MySQL полное сканирование таблицы после добавления дополнительных примерно 2000 строк в вашу таблицу user_metrics. В небольших таблицах доступ по индексу на самом деле дороже (с точки зрения ввода / вывода), чем сканирование таблицы, и оптимизатор MySQL может принять это во внимание.

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

При работе с оптимизаторами на основе затрат (Oracle, Postgres и т. Д.) Необходимо периодически запускать ANALYZE для ваших различных таблиц, поскольку их размер увеличивается более чем на 10-15 %. (По умолчанию Postgres сделает это для вас автоматически, тогда как другие РСУБД возложат эту ответственность на администратора баз данных, то есть на вас.) Благодаря статистическому анализу ANALYZE поможет оптимизатору лучше понять, сколько операций ввода-вывода (и другие связанные ресурсы, такие как ЦП, необходимые, например, для сортировки), будут задействованы при выборе между различными вариантами выполнения планов. Невыполнение ANALYZE может привести к очень плохим, иногда катастрофическим планировочным решениям (например, миллисекундные запросы занимают, иногда, часы из-за неправильных вложенных циклов на JOIN с.)

Если производительность по-прежнему неудовлетворительна после запуска ANALYZE, вы, как правило, сможете обойти проблему, используя подсказки, например, FORCE INDEX, тогда как в других случаях вы могли бы наткнуться на ошибку MySQL (например, эту более старую , которая могла бы вас укусить, если бы вы использовали Rails 'nested_set).

Теперь, , так как вы находитесь в приложении Rails , будет неудобно (и победит цель ActiveRecord) выдавать ваши пользовательские запросы с подсказками вместо того, чтобы продолжать использовать ActiveRecord - сгенерированные.

Я уже упоминал, что в нашем приложении Rails все SELECT запросов упали ниже 100 мс после переключения на Postgres, тогда как некоторые из сложных объединений, генерируемых ActiveRecord, иногда занимали бы 15 с или более с MySQL 5.1 из-за вложенных циклов со сканированием внутренней таблицы, даже когда индексы были доступны. Ни один оптимизатор не идеален, и вы должны знать о возможных вариантах. Другие потенциальные проблемы с производительностью, о которых следует знать, кроме оптимизации плана запросов, блокируются. Хотя это выходит за рамки вашей проблемы.

13 голосов
/ 25 февраля 2009

Попробуйте форсировать этот индекс:

SELECT `user_metrics`.*
FROM `user_metrics` FORCE INDEX (index_user_metrics_on_user_id)
WHERE (`user_metrics`.user_id IN (N,N,N,N,N,N,N,N,N,N,N,N))

Я только что проверил, он использует индекс для точно такого же запроса:

EXPLAIN EXTENDED
SELECT * FROM tests WHERE (test IN ('test 1', 'test 2', 'test 3', 'test 4', 'test 5', 'test 6', 'test 7', 'test 8', 'test 9'))

1, 'SIMPLE', 'tests', 'range', 'ix_test', 'ix_test', '602', '', 9, 100.00, 'Using where'
7 голосов
/ 26 февраля 2009

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

Какой процент строк соответствует вашему предложению IN?

3 голосов
/ 03 декабря 2016

Я знаю, что опаздываю на вечеринку. Но надеюсь, что смогу помочь кому-то еще с подобной проблемой.

В последнее время у меня та же проблема. Тогда я решаю использовать самосоединение, чтобы решить мою проблему. Проблема не в MySQL. Проблема в нас. Тип возврата из подзапроса - это отличие от нашей таблицы. Таким образом, мы должны привести тип подзапроса к типу столбца выбора. Ниже приведен пример кода:

select `user_metrics`.* 
from `user_metrics` um 
join (select `user_metrics`.`user_id` in (N, N, N, N) ) as temp 
on um.`user_id` = temp.`user_id`

Или мой собственный код:

Старый: (Не использовать индекс: ~ 4 с)

SELECT 
    `jxm_character`.*
FROM
    jxm_character
WHERE
    information_date IN (SELECT DISTINCT
            (information_date)
        FROM
            jxm_character
        WHERE
            information_date >= DATE_SUB('2016-12-2', INTERVAL 7 DAY))
        AND `jxm_character`.`ranking_type` = 1
        AND `jxm_character`.`character_id` = 3146089;

Новое: (используйте индекс: ~ 0,02 с)

SELECT 
    *
FROM
    jxm_character jc
        JOIN
    (SELECT DISTINCT
        (information_date)
    FROM
        jxm_character
    WHERE
        information_date >= DATE_SUB('2016-12-2', INTERVAL 7 DAY)) AS temp 
        ON jc.information_date = STR_TO_DATE(temp.information_date, '%Y-%m-%d')
        AND jc.ranking_type = 1
        AND jc.character_id = 3146089;

jxm_character:

  • Записи: ~ 3,5M
  • ПК: jxm_character (дата_информации, тип_ранга, идентификатор_символа)

SHOW VARIABLES LIKE '%version%';

'protocol_version', '10'
'version', '5.1.69-log'
'version_comment', 'Source distribution'

Последнее примечание. Убедитесь, что вы понимаете самое левое правило индекса MySQL.

P / s: Извините за мой плохой английский. Я публикую свой код (конечно же, рабочий), чтобы очистить свое решение: D.

0 голосов
/ 25 февраля 2009

Будет ли лучше, если вы уберете лишние скобки вокруг предложения where?

Хотя могло случиться так, что, поскольку у вас всего около 200 строк, было решено, что сканирование таблицы будет быстрее. Попробуйте использовать таблицу с большим количеством записей.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...