MySQL использует индексы для нескольких OR, но не использует индекс для IN и намного медленнее

Я был занят изменением некоторых SQL-запросов, чтобы они выглядели более читабельными для человеческого глаза, мне также сказали, что они будут, возможно, на 5-10% быстрее.

Предыдущие операторы SQL будут выглядеть так.

SELECT * FROM team WHERE Team1='Джо Блоггс' ИЛИ ​​Team2='Джо Блоггс' ИЛИ ​​Team3='Джо Блоггс'

я изменил его на

ВЫБЕРИТЕ * ИЗ команд, ГДЕ 'Джо Блоггс' В (Команда1,Команда2,Команда3)

Новый запрос примерно в 10 раз медленнее, после проверки того, что может быть причиной, я обнаружил, что он не использует никаких индексов, и даже если я попытаюсь принудительно ввести индекс, он все равно не будет его использовать.

В таблице около 120 000 строк, и я не могу изменить формат таблицы, так как другие приложения, к которым у меня нет доступа, используют ее. Все столбцы Team1,Team2,Team3 имеют тип VARCHAR(45)

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

Множественные ИЛИ SELECT (запустить 1000 раз без кэша) — истекло 12,863906860352 IN SELECT (выполнить 1000 раз без кэша) — истекло 122,73787903786

Спасибо за уделенное время.


person Twingo    schedule 26.07.2018    source источник
comment
Укажите EXPLAIN SELECT ... для двух версий. В частности, я ищу союз слияния индексов.   -  person Rick James    schedule 17.08.2018


Ответы (4)


В запросе:

SELECT * FROM teams WHERE 'Joe Bloggs' IN (Team1,Team2,Team3)

вы сравниваете (ищете) группу столбцов со строковым литералом. Оптимизатор обычно использует индекс цели поиска, в данном случае Joe Bloggs, чтобы найти значение в предложении IN. Но он не может поместить индекс в строковый литерал. Итак, здесь все наоборот, и поэтому индекс не поможет.

С другой стороны, в вашем первом запросе:

SELECT * FROM teams WHERE Team1='Joe Bloggs' OR Team2='Joe Bloggs' OR Team3='Joe Bloggs'

MySQL будет захватывать строковые литералы, а затем сравнивать их с различными столбцами, используя индекс B-дерева. Это ведет себя так, как вы ожидаете и видите.

person Tim Biegeleisen    schedule 26.07.2018
comment
. . Я не думаю, что MySQL использует индекс для OR, хотя более поздние версии могли реализовать эту оптимизацию. - person Gordon Linoff; 26.07.2018
comment
@GordonLinoff Но как тогда объяснить наблюдения ОП? - person Tim Biegeleisen; 26.07.2018
comment
Запрос OR определенно использует индексы, он намного быстрее, и использование EXPLAIN показывает, что он запрашивает только небольшое количество строк и индекс, который он использует, по сравнению с оператором IN, который запрашивает все строки в таблице и не использует индекс . - person Twingo; 26.07.2018
comment
Тим, по вашему опыту, должен ли я затем придерживаться нескольких OR, поскольку моя бенчмаркинг до сих пор, кажется, указывает, что в моем случае использования это самый быстрый, или это все еще более простой способ написать запрос, все еще используя индексы ? - person Twingo; 26.07.2018
comment
Что вам не нравится в вашем первоначальном запросе, который работает на вас, кроме его визуальной части? - person Tim Biegeleisen; 26.07.2018
comment
Это называется оптимизацией слияния индексов, см. dev.mysql. com/doc/refman/5.5/en/index-merge-optimization.html - person Shadow; 26.07.2018
comment
Тим, это чисто визуальная часть, в приведенном выше примере я проверяю столбцы только на 1 имя, в некоторых случаях это может быть до 15 имен, поэтому я просто использую один большой оператор ИЛИ, который выглядит грязно, но это самый быстрый способ, который я нашел до сих пор. Я благодарю вас за ваше время :) - person Twingo; 27.07.2018

У вас есть «перевернутый IN»; оптимизатор будет использовать индекс только для column in (value1, value2, value3).

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

SELECT * FROM teams WHERE Team1='Joe Bloggs'
UNION
SELECT * FROM teams WHERE Team2='Joe Bloggs'
UNION
SELECT * FROM teams WHERE Team3='Joe Bloggs'

Таблица будет запрашиваться 3 раза, но каждый раз будет использоваться индекс.

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

person Bohemian♦    schedule 26.07.2018
comment
Я ценю ответ, я никогда не знал, что такое перевернутый оператор IN, я изменил запрос точно так, как вы написали выше, но, к сожалению, после запуска теста примерно 10 раз, чтобы получить хорошее среднее значение, он почти вдвое медленнее, чем исходный ИЛИ утверждение. С 1000 итерациями, занимающими 28,898875951767 секунд. Это также противоречит главному, на что я нацелился, а именно удобочитаемости, мне и другим намного легче понять множественные OR, но, возможно, это просто потому, что мы привыкли рассматривать эти утверждения в течение длительного времени. Спасибо за ваш ответ :) - person Twingo; 26.07.2018
comment
@twingo Я только что придумал название «inverted IN», чтобы помочь описать шаблон (однако я буду продолжать использовать его с этого момента, потому что это звучит круто). Сожалеем, что переписывание не сработало для вас. Вы уверены, что определены 3 индекса - по одному индексу для каждого столбца? Если да, попробуйте запустить analyze teams. Однако может случиться так, что оптимизатор MySQL хорошо справляется со своей задачей, и ваш исходный запрос ИЛИ — это то, что вам нужно. - person Bohemian♦; 26.07.2018
comment
@Twingo - При выборе времени запустите дважды и выберите второе время. Первый может включать ввод-вывод; последующие запуски, вероятно, будут одинаковыми из-за кэширования данных. - person Rick James; 17.08.2018
comment
@Twingo. Написав OR в несколько строк и выровняв текст, OR будет так же четко читаться, как IN. - person Rick James; 17.08.2018

План A: используйте FULLTEXT (team1, team2, team3) и MATCH(team1, team2, team3) AGAINST ('+Joe +Briggs' IN BOOLEAN MODE). Есть много предостережений при использовании этого подхода, но, если он применим в вашем случае, он будет очень быстрым.

План B: Несмотря на то, что «невозможно изменить формат таблицы», вы можете сыграть в какую-нибудь игру с VIEW, чтобы избежать распространения массива (команд) по столбцам.

person Rick James    schedule 17.08.2018

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

Вы можете написать запрос следующим образом:

SELECT t.*
FROM teams t
WHERE Team1 = 'Joe Bloggs'
UNION ALL
SELECT t.*
FROM teams t
WHERE Team2 = 'Joe Bloggs' AND Team1 <> 'Joe Bloggs' 
UNION ALL
SELECT t.*
FROM teams t
WHERE Team3 =  'Joe Bloggs'
  AND Team2 <> 'Joe Bloggs'
  AND Team1 <> 'Joe Bloggs';

Это может использовать индексы на (Team1), (Team2, Team1) и (Team3, Team2, Team1).

person Gordon Linoff    schedule 26.07.2018
comment
Как я упоминал в комментарии выше, индексы, безусловно, используются с запросом множественного ИЛИ, используя EXPLAIN до того, как оператор покажет, что он использует индекс, и он показывает, что он запрашивает только небольшое количество ROWS, по сравнению с оператором IN, который выполняет полный сканирование таблицы и без использования индекса. Я действительно благодарю вас за то, что вы нашли время, чтобы написать свой ответ, к сожалению, он работал медленнее, чем простое использование нескольких ИЛИ, и это сделало чтение запроса человеком значительно более трудным для моих глаз, чем простое использование ИЛИ несколько раз. Хотя я полностью понимаю, что ваш путь, может быть, правильный способ сделать это. - person Twingo; 26.07.2018
comment
Да, MySQL может использовать оптимизацию слияния индексов, чтобы использовать индексы для таких or условий, см. dev.mysql.com/doc/refman/5.5/en/index-merge-optimization.html Как видите, эта функция доступна по крайней мере с версии 5.5. - person Shadow; 26.07.2018
comment
@Тень . . . Удивительно, что сканирование слиянием не будет использоваться для in списка столбцов. - person Gordon Linoff; 26.07.2018
comment
@GordonLinoff - история проектирования и разработки MySQL пронизана тем, что сделано достаточно, чтобы сказать, что функция реализована. ALTER было так просто (в реализации), когда он всегда перестраивал таблицу. Теперь в 5.6, 5.7 и 8.0 есть несколько попыток выжать все возможные оптимизации. Я наблюдал за EXPLAINs с тех пор, как было реализовано слияние индексов (в 4.1?). Он почти никогда не использовался, даже для запросов, которые выглядят вполне вероятными. Вероятно, он был нацелен только на IN, а не на JOIN и уж точно не на IN. Я бы сказал, что перевернутый ИН встречается крайне редко. - person Rick James; 17.08.2018
comment
@GordonLinoff - Еще один случай, когда функция существует, но не оптимизирована: WHERE (a, b) > (123, 345) -- INDEX(a,b) бесполезна. Обходной путь уродлив (хотя и оптимизирован). Это намного уродливее с 3+ столбцами. - person Rick James; 17.08.2018
comment
@GordonLinoff - оправдание неиспользования слияния индексов: другие поставщики используют ROWNUM, что делает слияние индексов блестящими; InnoDB обходит их, делая их менее полезными. - person Rick James; 17.08.2018