Индексы В Mysql: Многостолбцовые Индексы Против Комбинированных Индексов

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

Например, если мне нужно выполнить запрос типа ВОЗРАСТ=18 И ШТАТ='CA' , то многие люди просто создадут два отдельных индекса для столбцов AGE и STATE. Намного лучше ( здесь и далее ок.

переводчик: и обычно единственно верный ) Стратегия заключается в создании комбинированного индекса формы (AGE,STATE).

Давайте посмотрим, почему это так.

Обычно (но не всегда) индексы в MySQL Индексы BTREE — Этот тип индекса позволяет быстро просматривать информацию, содержащуюся в его префиксах, и перебирать диапазоны отсортированных значений.

Например, когда вы спрашиваете ВОЗРАСТ = 18 лет с индексом BTREE по столбцу AGE MySQL найдет первую строку в таблице, соответствующую запросу, и продолжит поиск до тех пор, пока не найдет первую неподходящую строку — тогда он останавливает поиск, т.к.

считает, что дальше ничего подходящего не будет. Диапазоны, например запросы типа МЕЖДУ 18 ДО 20 ЛЕТ , работают аналогично — MySQL останавливается на разных значениях.

Ситуация с запросами типа ВОЗРАСТ В (18,20,30) , потому что MySQL фактически должен пройти через индекс несколько раз.

Итак, мы обсудили, как MySQL осуществляет поиск по индексу, но не определили, что он возвращает после поиска — обычно (если мы не говорим о покрывающих индексах) он получает «указатель строки», который может быть значением первичного ключа.

(при использовании движка InnoDB), физическое смещение в файле (для MyISAM) или что-то в этом роде.

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

Какие варианты есть у MySQL, если вы создаете два отдельных индекса? Он может либо использовать только одну из них, чтобы выбрать подходящие строки (а затем отфильтровать извлеченные данные по ГДЕ — но без использования индексов ), или он может получить указатели на строки из всех соответствующих индексов и вычислить их пересечение, а затем вернуть данные.

Какой метод окажется более подходящим, зависит от избирательности и корреляции показателей.

Если после обработки WHERE в первом столбце выбирается 5% строк, а дальнейшее применение WHERE во втором столбце отфильтровывает строки до 1% от общего количества, то использование пересечений, конечно, имеет смысл.

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

Давайте посмотрим на несколько примеров:

CREATE TABLE `idxtest` ( `i1` int(10) UNSIGNED NOT NULL, `i2` int(10) UNSIGNED NOT NULL, `val` varchar(40) DEFAULT NULL, KEY `i1` (`i1`), KEY `i2` (`i2`), KEY `combined` (`i1`,`i2`) ) ENGINE=MyISAM DEFAULT CHARSET=latin1

Я сделал столбцы i1 и i2 независимыми друг от друга, при этом каждый столбец выбирал около 1% строк в таблице, содержащей в общей сложности 10 миллионов записей.



mysql [localhost] {msandbox} (test)> EXPLAIN SELECT avg(length(val)) FROM idxtest WHERE i1=50 AND i2=50; +----+-------------+---------+------+----------------+----------+---------+-------------+------+-------+ | id | select_type | TABLE | type | possible_keys | KEY | key_len | ref | rows | Extra +----+-------------+---------+------+----------------+----------+---------+-------------+------+-------+ | 1 | SIMPLE | idxtest | ref | i1,i2,combined | combined | 8 | const,const | 665 | +----+-------------+---------+------+----------------+----------+---------+-------------+------+-------+ 1 row IN SET (0.00 sec)

Как видите, MySQL решил использовать комбинированный индекс, и запрос был выполнен менее чем за 10 мс! Теперь предположим, что у нас есть индекс только для отдельных столбцов (прикажите оптимизатору игнорировать объединенный индекс):

mysql [localhost] {msandbox} (test)> EXPLAIN SELECT avg(length(val)) FROM idxtest IGNORE INDEX (combined) WHERE i1=50 AND i2=50; +----+-------------+---------+-------------+---------------+-------+---------+------+------+-------------------------------------+ | id | select_type | TABLE | type | possible_keys | KEY | key_len | ref | rows | Extra +----+-------------+---------+-------------+---------------+-------+---------+------+------+-------------------------------------+ | 1 | SIMPLE | idxtest | index_merge | i1,i2 | i1,i2 | 4,4 | NULL | 1032 | USING intersect(i1,i2); USING WHERE +----+-------------+---------+-------------+---------------+-------+---------+------+------+-------------------------------------+ 1 row IN SET (0.00 sec)

Как вы можете видеть в этом случае, MySQL искал пересечения индексов, и выполнение запроса заняло 70 мс — В 7 раз дольше! Теперь посмотрим, что произойдет, если мы будем использовать только один индекс и фильтровать полученные данные:

mysql [localhost] {msandbox} (test)> EXPLAIN SELECT avg(length(val)) FROM idxtest IGNORE INDEX (combined,i2) WHERE i1=50 AND i2=50; +----+-------------+---------+------+---------------+------+---------+-------+--------+-------------+ | id | select_type | TABLE | type | possible_keys | KEY | key_len | ref | rows | Extra +----+-------------+---------+------+---------------+------+---------+-------+--------+-------------+ | 1 | SIMPLE | idxtest | ref | i1 | i1 | 4 | const | 106222 | USING WHERE +----+-------------+---------+------+---------------+------+---------+-------+--------+-------------+ 1 row IN SET (0.00 sec)

На этот раз MySQL пришлось пройти значительно больше строк, и запрос занял 290 мс .

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

Однако на этом проблемы с пересечениями индексов не заканчиваются.

В настоящее время использование этой процедуры в MySQL существенно ограничено, поэтому MySQL использует ее не всегда:

mysql [localhost] {msandbox} (test)> EXPLAIN SELECT avg(length(val)) FROM idxtest IGNORE INDEX (combined) WHERE i1=50 AND i2 IN (49,50); +----+-------------+---------+------+---------------+------+---------+-------+--------+-------------+ | id | select_type | TABLE | type | possible_keys | KEY | key_len | ref | rows | Extra +----+-------------+---------+------+---------------+------+---------+-------+--------+-------------+ | 1 | SIMPLE | idxtest | ref | i1,i2 | i1 | 4 | const | 106222 | USING WHERE +----+-------------+---------+------+---------------+------+---------+-------+--------+-------------+ 1 row IN SET (0.00 sec)

Как только запрос по одному из столбцов станет перечислением, а не сравнением, MySQL больше не сможет использовать пересечение индексов, даже если в этом случае запрос i2 ИН (49,50) это было бы более чем разумно, потому что.

запрос остается весьма избирательным.

Теперь давайте проведем еще один тест. Я очистил таблицу и заново заполнил ее данными, чтобы значения в i1 и i2 были сильно коррелированы.

На самом деле теперь они в целом равны:

mysql [localhost] {msandbox} (test)> UPDATE idxtest SET i2=i1; Query OK, 10900996 rows affected (6 min 47.87 sec) Rows matched: 11010048 Changed: 10900996 Warnings: 0

Давайте посмотрим, что произойдет в этом случае:

mysql [localhost] {msandbox} (test)> EXPLAIN SELECT avg(length(val)) FROM idxtest WHERE i1=50 AND i2=50; +----+-------------+---------+-------------+----------------+-------+---------+------+------+-------------------------------------+ | id | select_type | TABLE | type | possible_keys | KEY | key_len | ref | rows | Extra +----+-------------+---------+-------------+----------------+-------+---------+------+------+-------------------------------------+ | 1 | SIMPLE | idxtest | index_merge | i1,i2,combined | i2,i1 | 4,4 | NULL | 959 | USING intersect(i2,i1); USING WHERE +----+-------------+---------+-------------+----------------+-------+---------+------+------+-------------------------------------+ 1 row IN SET (0.00 sec)

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

Также обратите внимание, что при оценке приблизительного количества строк существует большая погрешность.

Это связано с тем, что MySQL считает значения в столбцах i1 и i2 независимыми и поэтому выбирает пересечение индексов.

На самом деле он не может предположить обратного, потому что.

у него нет никакой статистики о соотношении значений в столбцах.



mysql [localhost] {msandbox} (test)> EXPLAIN SELECT avg(length(val)) FROM idxtest IGNORE INDEX(i2) WHERE i1=50 AND i2=50; +----+-------------+---------+------+---------------+------+---------+-------+--------+-------------+ | id | select_type | TABLE | type | possible_keys | KEY | key_len | ref | rows | Extra +----+-------------+---------+------+---------------+------+---------+-------+--------+-------------+ | 1 | SIMPLE | idxtest | ref | i1,combined | i1 | 4 | const | 106222 | USING WHERE +----+-------------+---------+------+---------------+------+---------+-------+--------+-------------+ 1 row IN SET (0.00 sec)

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

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

Повторное выполнение запроса заняло 290 мс - точно так же, как и в прошлый раз.

Давайте заставим MySQL использовать только комбинированный индекс:

mysql [localhost] {msandbox} (test)> EXPLAIN SELECT avg(length(val)) FROM idxtest IGNORE INDEX(i1,i2) WHERE i1=50 AND i2=50; +----+-------------+---------+------+---------------+----------+---------+-------------+--------+-------+ | id | select_type | TABLE | type | possible_keys | KEY | key_len | ref | rows | Extra +----+-------------+---------+------+---------------+----------+---------+-------------+--------+-------+ | 1 | SIMPLE | idxtest | ref | combined | combined | 8 | const,const | 121137 | +----+-------------+---------+------+---------------+----------+---------+-------------+--------+-------+ 1 row IN SET (0.00 sec)

Видно, что MySQL ошибается примерно на 20% при оценке количества итерируемых строк, что, конечно, неверно, поскольку используется тот же префикс, что и при использовании индекса только по столбцу i1. MySQL не знает этого, потому что.

просматривает статистику отдельных индексов и не пытается их согласовать.

Поскольку используемый комбинированный индекс больше, чем индекс по одному столбцу, запрос занял 300 мс .

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

Есть простые способы заставить MySQL не использовать индексы пересечений, но, к сожалению, я не знаю, как заставить его использовать пересечения, если он считает это неоптимальным.

Надеюсь, что эта функция будет добавлена в будущем.

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

Это тот случай, когда мы используем ИЛИ при выборке между столбцами.

В этом случае комбинированный индекс становится совершенно бесполезным, и у MySQL есть выбор между выполнением ПОЛНОГО СКАНИРОВАНИЯ таблицы и выполнением ОБЪЕДИНЕНИЯ значений вместо поиска пересечения данных, полученных из одной таблицы.

Я снова изменил значения в столбцах i1 и i2, чтобы они содержали независимые данные (типичная ситуация для таблиц).



mysql [localhost] {msandbox} (test)> EXPLAIN SELECT avg(length(val)) FROM idxtest WHERE i1=50 OR i2=50; +----+-------------+---------+-------------+----------------+-------+---------+------+--------+---------------------------------+ | id | select_type | TABLE | type | possible_keys | KEY | key_len | ref | rows | Extra +----+-------------+---------+-------------+----------------+-------+---------+------+--------+---------------------------------+ | 1 | SIMPLE | idxtest | index_merge | i1,i2,combined | i1,i2 | 4,4 | NULL | 203803 | USING union(i1,i2); USING WHERE +----+-------------+---------+-------------+----------------+-------+---------+------+--------+---------------------------------+ 1 row IN SET (0.00 sec)

Такие запросы выполняются 660 мс .

Отключив индекс по второму столбцу, мы получим ПОЛНОЕ СКАНИРОВАНИЕ:

mysql [localhost] {msandbox} (test)> EXPLAIN SELECT avg(length(val)) FROM idxtest IGNORE INDEX(i2) WHERE i1=50 OR i2=50; +----+-------------+---------+------+---------------+------+---------+------+----------+-------------+ | id | select_type | TABLE | type | possible_keys | KEY | key_len | ref | rows | Extra +----+-------------+---------+------+---------------+------+---------+------+----------+-------------+ | 1 | SIMPLE | idxtest | ALL | i1,combined | NULL | NULL | NULL | 11010048 | USING WHERE +----+-------------+---------+------+---------------+------+---------+------+----------+-------------+ 1 row IN SET (0.00 sec)

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

Выполнение такого запроса занимает 3370 мс ! Также обратите внимание, что выполнение запроса заняло в 5 раз больше времени, несмотря на то, что при ПОЛНОМ СКАНИРОВАНИИ просматривается примерно в 50 раз больше строк.

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

В случае с UNION оптимизатор более продвинутый и вполне способен работать с диапазонами:

mysql [localhost] {msandbox} (test)> EXPLAIN SELECT avg(length(val)) FROM idxtest WHERE i1=50 OR i2 IN (49,50); +----+-------------+---------+-------------+----------------+-------+---------+------+--------+--------------------------------------+ | id | select_type | TABLE | type | possible_keys | KEY | key_len | ref | rows | Extra +----+-------------+---------+-------------+----------------+-------+---------+------+--------+--------------------------------------+ | 1 | SIMPLE | idxtest | index_merge | i1,i2,combined | i1,i2 | 4,4 | NULL | 299364 | USING sort_union(i1,i2); USING WHERE +----+-------------+---------+-------------+----------------+-------+---------+------+--------+--------------------------------------+ 1 row IN SET (0.00 sec)



Подведение итогов

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

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

Теги: #MySQL #index #indexes #index #multicolumn #multicolumn indexes #MySQL

Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.