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)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.
Попробуйте заставить этот индекс:
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?