Почему MySQL использует индексное пересечение индекса вместо комбинированного индекса?
-
13-10-2019 - |
Вопрос
Время от времени я сталкиваюсь с странным поведением MySQL. Давайте предположим, что у меня есть индексы (тип, REL, создан), (тип), (REL). Лучший выбор для такого запроса:
SELECT id FROM tbl
WHERE rel = 3 AND type = 3
ORDER BY created;
было бы использовать индекс (type, rel, created)
Анкет Но MySQL решает пересечь индексы (type)
а также (rel)
, и это приводит к худшей производительности. Вот пример:
mysql> EXPLAIN
-> SELECT id FROM tbl
-> WHERE rel = 3 AND type = 3
-> ORDER BY created\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: tbl
type: index_merge
possible_keys: idx_type,idx_rel,idx_rel_type_created
key: idx_type,idx_rel
key_len: 1,2
ref: NULL
rows: 4343
Extra: Using intersect(idx_type,idx_rel); Using where; Using filesort
И тот же запрос, но с добавлением подсказки:
mysql> EXPLAIN
-> SELECT id FROM tbl USE INDEX (idx_type_rel_created)
-> WHERE rel = 3 AND type = 3
-> ORDER BY created\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: tbl
type: ref
possible_keys: idx_type_rel_created
key: idx_type_rel_created
key_len: 3
ref: const,const
rows: 8906
Extra: Using where
Я думаю, что MySQL принимает план выполнения, который содержит меньше числа в столбце «Строки» команды объяснения. С этой точки зрения, Index Mustsection с 4343 строками выглядит действительно лучше, чем использование моего комбинированного индекса с 8906 строками. Итак, может быть, проблема в этих числах?
mysql> SELECT COUNT(*) FROM tbl WHERE type=3 AND rel=3;
+----------+
| COUNT(*) |
+----------+
| 3056 |
+----------+
Из этого я могу сделать вывод, что MySQL ошибается при расчете приблизительного количества строк для комбинированного индекса.
Итак, что я могу сделать здесь, чтобы MySQL принял правильный план исполнения?
Я не могу использовать подсказки оптимизатора, потому что я должен придерживаться Django Orm, единственное решение, которое я обнаружил,-это удалить эти индексы с одним полем.
MySQL версия 5.1.49.
Структура таблицы:
CREATE TABLE tbl (
`id` int(11) NOT NULL AUTO_INCREMENT,
`type` tinyint(1) NOT NULL,
`rel` smallint(2) NOT NULL,
`created` datetime NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_type` (`type`),
KEY `idx_rel` (`rel`),
KEY `idx_type_rel_created` (`type`,`rel`,`created`)
) ENGINE=MyISAM;
Решение
Трудно точно сказать, почему MySQL
выбирает index_merge_intersection
Над индексным сканированием, но вы должны отметить, что при составных индексах статистика вплоть до данного столбца хранятся для композитных индексов.
Значение information_schema.statistics.cardinality
для столбца type
композитного индекса покажет кардинальность (rel, type)
, нет type
сам.
Если есть корреляция между rel
а также type
, затем кардинальность (rel, type)
будет меньше, чем продукт кардинальности rel
а также type
взято отдельно от индексов на соответствующих столбцах.
Вот почему количество строк рассчитывается неправильно (пересечение не может быть больше по размеру, чем союз).
Вы можете запретить index_merge_intersection
установив его в @@optimizer_switch
:
SET optimizer_switch = 'index_merge_intersection=off'
Другие советы
Другое дело стоит упомянуть: у вас не будет проблемы, если бы вы удалили индекс только на типе. Индекс не требуется, поскольку он дублирует часть композитного индекса.