MySQL не использует индексы с предложением WHERE IN?

Я пытаюсь оптимизировать некоторые запросы к базе данных в моем приложении Rails, и у меня есть несколько, которые поставили меня в тупик. Все они используют предложение IN 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)

Посмотрите, как MySQL использует индексы .

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

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

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

If performance is still unsatisfactory after running ANALYZE, then you will typically be able to work around the issue by using hints, e.g. FORCE INDEX, whereas in other cases you might have stumbled over a MySQL bug (e.g. this older one, which could have bitten you were you to use Rails' nested_set ).

Now, since you are in a Rails app, it will be cumbersome (and defeat the purpose of ActiveRecord ) to issue your custom queries with hints instead of continuing to use the ActiveRecord -generated ones.

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

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

В последнее время у меня такая же проблема. Затем я решаю использовать самосоединение для решения моей проблемы. Проблема не в 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,5 млн
  • ПК: jxm_character (информация_дата, рейтинг_типа, идентификатор_символа)

SHOW VARIABLES LIKE '%version%';

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

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

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

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

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

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

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'

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

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