Я дошел до того, что не могу понять, почему следующий запрос MySQL становится медленнее, когда я использую индекс в своем предложении where. Столбец, который сводит меня с ума, называется удаленным. Таблица содержит 4,8 млн строк.

Запрос:

SELECT SQL_NO_CACHE SUM(amount)/100 FROM transactions WHERE (type="Payment" or type="Refund") and deleted is NULL

Этот запрос занимает чуть более 11 секунд, когда столбец является индексом, и 3 секунды, когда он не индексируется, или когда я использую USE INDEX(), которые говорят оптимизатору не использовать какой-либо индекс.

MySQL версии 5.6, протестирован в AWS Aurora db.r5.xlarge (4 ЦП / 32 ГБ)

Структура таблицы:

id int(11) NOT NULL, type enum('Charge','Payment','Refund','Credit Adjustment','Debit Adjustment','Transfer') NOT NULL, amount int(11) NOT NULL, deleted datetime DEFAULT NULL, deleted_by int(11) DEFAULT NULL ENGINE=InnoDB DEFAULT CHARSET=utf8; ADD KEY type (type), ADD KEY deleted (deleted)

Буду признателен за любые подсказки здесь!

0
Asparuh Nestorov 8 Окт 2019 в 23:32
4
Вы смотрели на вывод EXPLAIN с индексом и без него?
 – 
PaulProgrammer
8 Окт 2019 в 23:34
Какой процент таблицы содержит deleted IS NULL?
 – 
Rick James
15 Окт 2019 в 07:18

4 ответа

Я использовал «объяснять», чтобы проверить приведенный выше запрос, можно ли использовать индекс или нет. В результате индекс не работает ни для оператора «ИЛИ», ни для оператора «IN», поэтому я думаю, что «UNION» - лучший выбор. И я думаю, вам не нужно добавлять индекс для «удаленного» столбца, потому что он тоже не работает.

Результат "объяснения" для оператора IN: "объясните" результат для оператора IN

Объясните результат для оператора ИЛИ: "объясните" результат для оператора ИЛИ

Результат "объединения": "union" result

индекс по столбцу "удалено" не работает: индекс в столбце

1
ascripter 9 Окт 2019 в 17:49
1
Пожалуйста, размещайте изображения напрямую, а не внешние ссылки.
 – 
tremendows
9 Окт 2019 в 16:23
MySQL пропустит использование индекса, если значения, которые вы ищете, соответствуют большой части таблицы. Порог не задокументирован, но по моему опыту это примерно 20% от таблицы. Было бы разумно сказать, что каждое значение типа встречается менее чем в 20% таблицы, но вместе они соответствуют более чем 20%? И я предполагаю, что deleted is NULL соответствует более чем 20% строк.
 – 
Bill Karwin
9 Окт 2019 в 20:22

Думаю, мне пришла в голову логическая идея, почему использование индексированного столбца вызывает задержку. Проблема должна быть в данных этого столбца и особенно в его сильно искаженном распределении уникальных значений - соответственно трех двоичных узлов. Он состоит из 4,8 M строк с одинаковым значением NULL и всего 30 K строк с 3 K уникальными значениями.

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

  2. Данные в этом удаленном столбце увеличивают мощность удаленного индекса и делают его предпочтительным для оптимизатора по сравнению с индексом столбца типа, который имеет мощность всего 10. Если распределение значений в обоих столбцах было нормальным, то логично установить приоритет, используя столбец с более высокой мощностью и результатом меньшее подмножество для дальнейшей обработки. Однако распределение значений удаленных столбцов очень искажено в сторону нулевых значений. Таким же образом, как описано выше, использование удаленного индекса для поиска нулевых значений добавляет много накладных расходов, но мало влияет на производительность, предотвращает использование других более релевантных индексов и, следовательно, задержку результатов.

0
Asparuh Nestorov 9 Окт 2019 в 15:55

Если вы удалите индекс только на deleted и добавите этот "составной" индекс:

INDEX(deleted, type)   -- in this order

Он может работать быстрее. Обратите внимание, что столбец = идет первым (считается IS NULL), затем IN (в который превращается ваш OR).

Еще быстрее можно было бы сделать индекс «покрывающим»:

INDEX(deleted, type, amount)   -- in this order

Превращение OR в UNION - хороший трюк, но здесь не обязателен.

Если deleted редко бывает NULL, тогда Оптимизатор может предпочесть этот индекс, даже если он окажется менее эффективным. (Это может объяснить проблему, которую вы представляете. Мой составной индекс позволяет избежать этой проблемы.)

Независимый вопрос: почему deleted? Разве у вас не может быть просто deleted_by быть NULL, чтобы указать на то же самое?

0
Rick James 15 Окт 2019 в 07:30

(Изменить: по-видимому, это неверно для данной конкретной ситуации. Этот ответ применяется только в том случае, если условия OR'd включают разные поля .... или создают проверку диапазона, которая предотвращает использование полей дальше в индексе. Подробнее см. В комментариях. )

MySQL не очень хорошо использует преимущества индексов, когда они представлены с условиями OR. Часто вы можете ускорить запрос, например

SELECT a FROM b WHERE y = n1 OR y = n2

Расширив его до такого союза

SELECT a FROM b WHERE y = n1
UNION 
SELECT a FROM b WHERE y = n2

Я слышал, что более поздние версии сделали такие условия, выраженные в форме y IN (n1, n2), немного более эффективными, но моя основная работа в последние несколько лет была в MS SQL, поэтому я не могу сказать, насколько это улучшен.

Это можно использовать даже в случае прямого суммирования с небольшим расширением ....

SELECT SUM(subt) 
FROM (
   SELECT SUM(amount)/100 AS subt FROM transactions WHERE type="Payment" and deleted is NULL
   UNION 
   SELECT SUM(amount)/100 AS subt FROM transactions WHERE type="Refund" and deleted is NULL
) AS subq
0
Uueerdo 15 Окт 2019 в 19:16
2
MySQL прекрасно справляется с условиями OR - если все термины ищут в одном столбце. Это эквивалент предикату IN(). Это не только последние версии. В этом случае MySQL уже много лет использует индекс. Обходной путь UNION требуется только в том случае, если каждое из условий OR выполняет поиск в разных столбцах. См. Мой старый ответ здесь: stackoverflow.com/a/13866221/20860
 – 
Bill Karwin
9 Окт 2019 в 01:08
Я полагаюсь на ваш опыт; несколько раз мне приходилось делать что-то подобное, это действительно было для разных столбцов. Но подразумеваем ли мы то же самое под словом «недавний»? Вопрос касается версии 5.6, выпущенной более 6 лет назад. Я думал, что оптимизация IN появилась только в 8.x.
 – 
Uueerdo
9 Окт 2019 в 01:14
В 8.x могут быть новые оптимизации, но MySQL определенно использует индексы для запросов диапазона в течение многих лет. Вот документ для 5.5, примерно 2010: dev.mysql.com/ doc / refman / 5.5 / en / range-optimisation.html Это не самая старая версия MySQL, поддерживающая использование индексов для запросов диапазона, это просто самый старый документ, который все еще доступен.
 – 
Bill Karwin
9 Окт 2019 в 01:18
Ах, у меня сложилось ошибочное впечатление, что условия ИЛИ в том же поле все еще не интерпретировались как «условие диапазона».
 – 
Uueerdo
9 Окт 2019 в 01:30
1
- Исправление к вашему редактированию: есть и другие ситуации, когда переход на UNION полезен, даже если задействованы те же столбцы. Например (я думаю): WHERE x=1 AND y IN(2,3) AND z>4 с INDEX(x,y,z). В этом случае он получает возможность использовать z.
 – 
Rick James
15 Окт 2019 в 07:27