Почему - или когда - MySQL не использует индексы для условий ИЛИ, если он использует для условий И?

У меня есть таблица the_table с атрибутами the_table.id, the_table.firstVal и the_table.secondVal (конечно, первичный ключ the_table.id).

После определения индекса для первого неключевого атрибута следующим образом:

CREATE INDEX idx_firstval  
ON the_table (firstVal);

Результат EXPLAIN для следующего дизъюнктивного (OR) запроса

SELECT * FROM the_table WHERE the_table.firstVal = 'A' OR the_table.secondVal = 'B';

is

| id    | select_type | table     | type    | possible_keys | key   | key_len   | ref   | rows  | Extra
| 1     | SIMPLE      | the_table | ALL     | idx_firstval  | NULL  | NULL      | NULL  | 3436  | Using where

что показывает, что индекс idx_firstval не используется. Теперь результат EXPLAIN для следующего конъюнктивного (AND) запроса

SELECT * FROM the_table WHERE the_table.firstVal = 'A' AND the_table.secondVal = 'B';

is

| id    | select_type   | table     | type  | possible_keys | key           | key_len   | ref   | rows  | Extra 
| 1     | SIMPLE        | the_table | ref   | idx_firstval  | idx_firstval  | 767       | const | 124   | Using index condition; Using where

который показывает используемый индекс на этот раз.

Почему MySQL предпочитает не использовать индексы для дизъюнктивного запроса, а для конъюнктивного?

Я просмотрел SO и, как было предложено в ответе в this thread, «использование OR в запросе часто приводит к тому, что оптимизатор запросов отказывается от поиска по индексу и возвращается к сканированию». Однако это не отвечает на вопрос, почему это происходит, а просто делает.

Другой поток пытается ответить, почему разделительный запрос не не использовать индексы, но я думаю, что это не удается - просто делается вывод, что OP использует небольшую базу данных. Я хочу знать разницу между дизъюнктивным и конъюнктивным падежом.


person Mew    schedule 04.05.2020    source источник


Ответы (2)


Поскольку план выполнения MySQL использует только один индекс для таблицы.

Если MySQL использует сканирование диапазона в столбце idx_firstval для удовлетворения предиката равенства в столбце firstVal, это оставляет MySQL все еще нуждающимся в проверке условия в столбце secondVal.


С AND MySQL нужно только проверить строки, возвращенные из сканирования диапазона индекса. Набор строк, которые необходимо проверить, ограничен условием.


С OR MySQL необходимо проверить строки, которые не были возвращены сканированием диапазона индекса, все остальные строки в таблице. Без индекса это означает полное сканирование таблицы. И если мы делаем полное сканирование таблицы, чтобы проверить secondVal, тогда будет дешевле проверить оба условия при сканировании (т. е. план, который включает доступ к индексу, а также полное сканирование, будет дороже).

(Если доступен составной индекс, содержащий как firstVal, так и secondVal, то для запроса OR оптимизатор может подумать, что дешевле проверить все строки в таблице, выполнив полное сканирование индекса, а затем просматривая данные. страницы.)


Когда мы понимаем, какие операции доступны оптимизатору, это приводит нас к тому, чтобы избегать OR и переписывать запрос, чтобы возвращать эквивалентный набор результатов с шаблоном запроса, который более явно определяет комбинацию двух наборов.

SELECT a.*
  FROM the_table a
 WHERE a.firstVal = 'A'

UNION ALL

SELECT b.*
  FROM the_table b
 WHERE b.secondVal = 'B'
   AND NOT ( b.firstVal <=> 'A' )

(Добавьте ORDER BY, если мы ожидаем, что строки будут возвращены в определенном порядке)

person spencer7593    schedule 04.05.2020
comment
Относительно того, доступен ли составной индекс, содержащий как firstVal, так и secondVal (...): я проверил составной индекс, как было предложено @TimBiegeleisen, и OR по-прежнему выполняет полное сканирование таблицы без использования индекса. Это действительно странно для меня; Я думаю, что оптимизатор желает использовать такие индексы в обоих случаях, но не для дизъюнктивного запроса. - person Mew; 04.05.2020
comment
Для тривиальных наборов стоимость полного сканирования невелика, поэтому оптимизатор может отдать предпочтение плану доступа с полным сканированием таблицы, а не с использованием индекса. Если план, использующий индекс, будет иметь доступ к страницам из индекса и к страницам из таблицы данных... для очень маленькой таблицы, совсем не удивительно, что оценка затрат для плана ниже. который игнорирует индекс и просто получает страницы из таблицы. - person spencer7593; 04.05.2020
comment
С * в списке SELECT MySQL должен будет посетить страницы данных в базовой таблице для любых столбцов, недоступных в индексе. Если доступен покрывающий индекс для запроса, мы, скорее всего, увидим, что индекс используется. Это не будет операция сканирования диапазона, поскольку она должна проверять каждую строку, то есть полное сканирование, но она может выполнять сканирование индекса вместо таблицы. Попробуйте отказаться от * в списке SELECT и сделать так, чтобы в индексе были доступны только столбцы запроса, например. SELECT t.firstVal, t.secondVal FROM the_table t WHERE ... OR ... - person spencer7593; 04.05.2020

Я удивлен, что MySQL использует индекс для любого из двух запросов. Правильным индексом для использования здесь будет составной индекс, который охватывает два столбца в предложении WHERE:

CREATE INDEX idx ON the_table (firstVal, secondVal);

Что касается того, почему MySQL использует индекс во втором случае, возможно, что большинство записей в the_table имеют firstVal значения, которые не A. В этом случае простое знание того, что равенство the_table.firstVal = 'A' является ложным, означало бы, что весь результат предложения WHERE будет известен (как ложный). Таким образом, ответ на вопрос, почему используется индекс, может быть как-то связан с количеством элементов ваших точных данных. Но в любом случае рассмотрите возможность использования составного индекса для охвата всех баз.

person Tim Biegeleisen    schedule 04.05.2020
comment
Вы не ошиблись, что «A» — редкое значение в этой таблице (124 записи), поэтому я сделал несколько дополнительных запросов для контекста. Два наиболее распространенных значения (1747 и 1446 записей соответственно, назовите их «C» и «D») имеют одинаковые EXPLAINs. Определив составной индекс в соответствии с вашим предложением, AND переключается на него, но OR по-прежнему выполняет полное сканирование таблицы. - person Mew; 04.05.2020
comment
@Mew Я не знаю ваших фактических данных или количества записей, но иногда база данных SQL даже не будет использовать индекс, если записей слишком мало. - person Tim Biegeleisen; 04.05.2020
comment
@Mew: если составной индекс является покрывающим индексом для запроса, мы можем увидеть полное сканирование индекса для запроса OR, если все столбцы, на которые есть ссылки в запросе, включены в индекс. например запрос, который отбрасывает * в списке SELECT и вместо этого выполняет SELECT firstVal, secondVal FROM the_table ...` (оптимизатор не может использовать операцию сканирования диапазона, ему все равно нужно проверять каждую строку, но если он может удовлетворить запрос полностью из индекса без необходимости поиска страниц в базовой таблице, мы называем это покрывающим индексом для запроса). - person spencer7593; 04.05.2020
comment
@TimBiegeleisen: Ах, может быть, мне следовало уточнить это конкретно: 3436 строк в the_table, как показано в первом выводе в моем посте (в столбце rows). - person Mew; 04.05.2020
comment
Это довольно маленький стол. Я предполагаю, что в случае OR индекс не поможет, поэтому MySQL просто возвращается к сканированию таблицы. - person Tim Biegeleisen; 04.05.2020
comment
@ spencer7593: Возможно, вам следует уточнить это в своем ответе, потому что на самом деле это было четкое понимание. Я проверил базу данных, и это правда, что если я заменю * на the_table.firstVal, the_table.secondVal (то есть больше не буду запрашивать the_table.id), EXPLAIN покажет type | ìndex, и действительно будет использован полный индекс. - person Mew; 04.05.2020
comment
Повышение, которое вы получаете за покрытие пункта SELECT, обычно невелико. - person Tim Biegeleisen; 04.05.2020
comment
@TimBiegeleisen: в аналогичной таблице с 26789 строками OR по-прежнему не использует составной индекс, но его все равно можно считать небольшим. - person Mew; 04.05.2020