Индекс MySQL для числовых столбцов замедляет запрос - PullRequest
4 голосов
/ 15 декабря 2010

У меня проблема с оптимизацией довольно большой таблицы (~ 1,7 млн ​​строк).

При выборе строк используются два столбца, назовем их colA и colB. Они оба имеют тип 'double' (5 десятичных знаков) и варьируются от:

колА: -90 ~ 90 colB: -180 ~ 180

Без индекса любой запрос вида:

SELECT * FROM table where colA BETWEEEN a and b AND colB BETWEEN c and d
Для выполнения

требуется примерно одинаковое время (~ 1 секунда), независимо от диапазона (a, b) и (c, d) (поскольку MySQL должен проверять каждую строку).

Если я добавлю индекс к colA и colB, произойдут две вещи: запросы, в которых диапазон (a, b) и (c, d) мал, например:

SELECT * FROM table where colA BETWEEEN -4 and 4 AND colB BETWEEN 3 and 7

бегать очень быстро (~ 1/10 секунды). Однако время выполнения увеличивается с увеличением диапазона между запрашиваемыми значениями. Например:

 SELECT * FROM table where colA BETWEEEN -80 and 80 AND colB BETWEEN -150 and 150

выполнение занимает около минуты.

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

Если бы кто-нибудь мог подсказать, как оптимизировать этот запрос, я был бы благодарен. Одна мысль состоит в том, чтобы использовать индекс для небольших диапазонов и запретить MySQL использовать его для больших диапазонов, однако я не смог найти команду, которая позволяет это.

Спасибо

РЕДАКТИРОВАТЬ: объясняет

Есть кое-что, что я тупо забыл упомянуть. Результаты упорядочены по rand () - я знаю, насколько это неэффективно, но я не мог найти другого способа получить произвольно ограниченное количество строк из таблицы.

Добавление rand () не влияет на время выполнения, когда нет индекса, но резко увеличивает время, необходимое, когда оно есть.

EDIT2: это использование составных индексов.

МАЛЫЙ ДИАПАЗОН:

"объяснить выбор * из таблицы, где colA между 35 и 38 и colB между -10 и 5 ORDER BY RAND () LIMIT 20"

9783 строки

NO INDEX (быстрый)

+----+-------------+-------+------+---------------+------+---------+------+---------+-------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows    | Extra       |
+----+-------------+-------+------+---------------+------+---------+------+---------+-------------+
|  1 | SIMPLE      | table | ALL  | NULL          | NULL | NULL    | NULL | 1673784 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+---------+-------------+

С ИНДЕКСОМ (очень быстро)

+----+-------------+-------+-------+---------------+------+---------+------+--------+-------------+
| id | select_type | table | type  | possible_keys | key  | key_len | ref  | rows   | Extra       |
+----+-------------+-------+-------+---------------+------+---------+------+--------+-------------+
|  1 | SIMPLE      | table | range | test          | test | 18      | NULL | 136222 | Using where |
+----+-------------+-------+-------+---------------+------+---------+------+--------+-------------+



БОЛЬШОЙ ДИАПАЗОН:

"объяснить выбор * из таблицы, где colA между -80 и 80 и colB между -150 и 150 ORDER BY RAND () LIMIT 20;"

1631862 строк

NO INDEX (быстрый)

+----+-------------+-------+------+---------------+------+---------+------+---------+-------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows    | Extra       |
+----+-------------+-------+------+---------------+------+---------+------+---------+-------------+
|  1 | SIMPLE      | table | ALL  | NULL          | NULL | NULL    | NULL | 1673784 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+---------+-------------+

С ИНДЕКСОМ (очень медленно:> 60 секунд)

+----+-------------+-------+------+---------------+------+---------+------+---------+-------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows    | Extra       |
+----+-------------+-------+------+---------------+------+---------+------+---------+-------------+
|  1 | SIMPLE      | table | ALL  | test          | NULL | NULL    | NULL | 1673784 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+---------+-------------+

EDIT3:

Подводя итог: (все запросы ограничены возвращением 20 строк)

большой диапазон с rand () с индексом: 45 секунд
большой диапазон без ранда (), с индексом: 0,003 секунды

большой диапазон с рандом, без индекса: 1 секунда
большой диапазон без ранда, без индекса: 0,003 секунды

Аномалия: «большой диапазон с rand () с индексом, 45 секунд».

Ответы [ 3 ]

5 голосов
/ 15 декабря 2010

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

Они работаютдля чисел то же самое, что и для строк.

Без индекса выполнение запроса занимает примерно одинаковое время (~ 1 секунда), независимо от диапазона (a, b), и(c, d)

Время выполнения полного сканирования таблицы существенно не зависит от содержимого условия WHERE.Время, затраченное на путь доступа к индексу, пропорционально количеству возвращаемых строк.Если запрос выбирает значительную часть таблицы, использование индекса всегда будет медленнее, чем не использование индекса.

Путь доступа к индексу эффективен только в том случае, если селективность индекса достаточна, т. Е. Количество извлеченных строкмал (некоторые говорят, что самое большее 10%).Время выполнения будет примерно пропорционально количеству возвращаемых строк и может закончиться медленнее, чем полное сканирование таблицы.

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

Оптимизатор запросов должен использовать статистику и эвристику, чтобы определить, следует ли использовать индекс.Возможно, вам нужно обновить эту статистику, используя OPTIMIZE TABLE .Если он по-прежнему не может принять правильное решение, вы можете помочь ему с подсказками .

SELECT * FROM table 
   IGNORE INDEX (the_index)
   where colA BETWEEEN -80 and 80 AND colB BETWEEN -150 and 150

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


Теперь, когда вы упомянули LIMIT 20, это становится более понятным:

большой диапазон с rand () с индексом: 45 секунд

NESTED LOOP со многими результатами + SORT

ПолучитьВСЕ записи (в диапазоне) из индекса, извлекают их одну за другой из таблицы, затем сортируют, затем ограничивают до 20

большой диапазон без rand (), с индексом: 0,003 секунды

NESTED LOOP прервано для 20 записей

Получить 20 записей из индекса, извлечь их одну из таблицы и вернуть их.Без сортировки, по сути, без большого диапазона.

большой диапазон с рандом, без индекса: 1 секунда

ПОЛНЫЙ СКАНИРОВАНИЕ + СОРТИРОВКА

Прочитатьвсю таблицу, сохранить то, что находится в диапазоне, затем отсортировать, затем ограничить до 20

большой диапазон без ранда, без индекса: 0,003 секунды

FULL TABLE SCAN, прерванона 20 записях

Начните читать таблицу, держите то, что находится в диапазоне, остановитесь, когда у вас будет 20, и вернитесь.

0 голосов
/ 15 декабря 2010

Последний запрос не должен занимать больше времени, чем первый.MySQL может не обновлять индекс, см. OPTIMIZE TABLE

Также вы можете проверить, как он планирует запрос, с помощью EXPLAIN и EXPLAIN ANALYZE.

Наконец, вы можете принудительно отключить индекс с помощью IGNORE INDEX (idx_name)

0 голосов
/ 15 декабря 2010

Индексы с множеством дубликатов являются пустой тратой.

Убедитесь, что ваш индекс использует оба поля;

create index idx_faster on tbl_mytbl (colA,colB)

для colB вы можете добавить еще

create index idx_colb on tbl_mytbl (colB)

С уважением, / Т

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...