Действительно ли необходимо индексировать все выбранные столбцы, чтобы MySQL мог использовать индекс?
Это загруженный вопрос, потому что есть факторы, которые определяют, стоит ли использовать индекс.
ФАКТОР № 1
Для какого данного индекса, какое ключевое население? Другими словами, каково количество элементов (различное число) всех кортежей, зарегистрированных в индексе?
ФАКТОР № 2
Какой механизм хранения вы используете? Все ли необходимые столбцы доступны из индекса?
ЧТО ДАЛЬШЕ ???
Давайте рассмотрим простой пример: таблица, содержащая два значения (мужское и женское)
Давайте создадим такую таблицу с тестом на использование индекса
USE test
DROP TABLE IF EXISTS mf;
CREATE TABLE mf
(
id int not null auto_increment,
gender char(1),
primary key (id),
key (gender)
) ENGINE=InnODB;
INSERT INTO mf (gender) VALUES
('M'),('M'),('M'),('M'),('M'),('M'),('M'),('M'),
('M'),('M'),('M'),('M'),('F'),('F'),('M'),('M'),
('M'),('M'),('M'),('M'),('M'),('M'),('M'),('M'),
('M'),('M'),('M'),('M'),('M'),('M'),('M'),('M'),
('F'),('M'),('M'),('M'),('M'),('M'),('M'),('M');
ANALYZE TABLE mf;
EXPLAIN SELECT gender FROM mf WHERE gender='F';
EXPLAIN SELECT gender FROM mf WHERE gender='M';
EXPLAIN SELECT id FROM mf WHERE gender='F';
EXPLAIN SELECT id FROM mf WHERE gender='M';
ТЕСТ InnoDB
mysql> USE test
Database changed
mysql> DROP TABLE IF EXISTS mf;
Query OK, 0 rows affected (0.00 sec)
mysql> CREATE TABLE mf
-> (
-> id int not null auto_increment,
-> gender char(1),
-> primary key (id),
-> key (gender)
-> ) ENGINE=InnoDB;
Query OK, 0 rows affected (0.07 sec)
mysql> INSERT INTO mf (gender) VALUES
-> ('M'),('M'),('M'),('M'),('M'),('M'),('M'),('M'),
-> ('M'),('M'),('M'),('M'),('F'),('F'),('M'),('M'),
-> ('M'),('M'),('M'),('M'),('M'),('M'),('M'),('M'),
-> ('M'),('M'),('M'),('M'),('M'),('M'),('M'),('M'),
-> ('F'),('M'),('M'),('M'),('M'),('M'),('M'),('M');
Query OK, 40 rows affected (0.06 sec)
Records: 40 Duplicates: 0 Warnings: 0
mysql> ANALYZE TABLE mf;
+---------+---------+----------+----------+
| Table | Op | Msg_type | Msg_text |
+---------+---------+----------+----------+
| test.mf | analyze | status | OK |
+---------+---------+----------+----------+
1 row in set (0.00 sec)
mysql> EXPLAIN SELECT gender FROM mf WHERE gender='F';
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| 1 | SIMPLE | mf | ref | gender | gender | 2 | const | 3 | Using where; Using index |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
1 row in set (0.00 sec)
mysql> EXPLAIN SELECT gender FROM mf WHERE gender='M';
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| 1 | SIMPLE | mf | ref | gender | gender | 2 | const | 37 | Using where; Using index |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
1 row in set (0.00 sec)
mysql> EXPLAIN SELECT id FROM mf WHERE gender='F';
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| 1 | SIMPLE | mf | ref | gender | gender | 2 | const | 3 | Using where; Using index |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
1 row in set (0.00 sec)
mysql> EXPLAIN SELECT id FROM mf WHERE gender='M';
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| 1 | SIMPLE | mf | ref | gender | gender | 2 | const | 37 | Using where; Using index |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
1 row in set (0.00 sec)
mysql>
ТЕСТ MyISAM
mysql> USE test
Database changed
mysql> DROP TABLE IF EXISTS mf;
Query OK, 0 rows affected (0.00 sec)
mysql> CREATE TABLE mf
-> (
-> id int not null auto_increment,
-> gender char(1),
-> primary key (id),
-> key (gender)
-> ) ENGINE=MyISAM;
Query OK, 0 rows affected (0.05 sec)
mysql> INSERT INTO mf (gender) VALUES
-> ('M'),('M'),('M'),('M'),('M'),('M'),('M'),('M'),
-> ('M'),('M'),('M'),('M'),('F'),('F'),('M'),('M'),
-> ('M'),('M'),('M'),('M'),('M'),('M'),('M'),('M'),
-> ('M'),('M'),('M'),('M'),('M'),('M'),('M'),('M'),
-> ('F'),('M'),('M'),('M'),('M'),('M'),('M'),('M');
Query OK, 40 rows affected (0.00 sec)
Records: 40 Duplicates: 0 Warnings: 0
mysql> ANALYZE TABLE mf;
+---------+---------+----------+----------+
| Table | Op | Msg_type | Msg_text |
+---------+---------+----------+----------+
| test.mf | analyze | status | OK |
+---------+---------+----------+----------+
1 row in set (0.00 sec)
mysql> EXPLAIN SELECT gender FROM mf WHERE gender='F';
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| 1 | SIMPLE | mf | ref | gender | gender | 2 | const | 3 | Using where; Using index |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
1 row in set (0.00 sec)
mysql> EXPLAIN SELECT gender FROM mf WHERE gender='M';
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| 1 | SIMPLE | mf | ref | gender | gender | 2 | const | 36 | Using where; Using index |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
1 row in set (0.00 sec)
mysql> EXPLAIN SELECT id FROM mf WHERE gender='F';
+----+-------------+-------+------+---------------+--------+---------+-------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+--------+---------+-------+------+-------------+
| 1 | SIMPLE | mf | ref | gender | gender | 2 | const | 3 | Using where |
+----+-------------+-------+------+---------------+--------+---------+-------+------+-------------+
1 row in set (0.00 sec)
mysql> EXPLAIN SELECT id FROM mf WHERE gender='M';
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| 1 | SIMPLE | mf | ALL | gender | NULL | NULL | NULL | 40 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
1 row in set (0.00 sec)
mysql>
Анализ для InnoDB
Когда данные были загружены как InnoDB, обратите внимание, что все четыре EXPLAIN
плана использовали gender
индекс. Третий и четвертый EXPLAIN
планы использовали gender
индекс, хотя запрошенные данные были id
. Почему? Потому id
что в PRIMARY KEY
и все вторичные индексы имеют ссылки на указатели PRIMARY KEY
(через gen_clust_index ).
Анализ для MyISAM
Когда данные были загружены как MyISAM, обратите внимание, что первые три EXPLAIN
плана использовали gender
индекс. В четвертом EXPLAIN
плане Оптимизатор запросов решил вообще не использовать индекс. Вместо этого он выбрал полное сканирование таблицы. Почему?
Независимо от СУБД Оптимизаторы запросов работают по очень простому практическому правилу: если индекс отбирается как кандидат для использования при выполнении поиска, а Оптимизатор запросов вычисляет, что он должен искать более 5% от общего числа строки в таблице:
- полное сканирование индекса выполняется, если все необходимые столбцы для поиска находятся в выбранном индексе
- полное сканирование таблицы в противном случае
ВЫВОД
Если у вас нет подходящих индексов покрытия или если ключевая совокупность для любого данного кортежа составляет более 5% таблицы, должно произойти шесть вещей:
- Осознайте, что вы должны профилировать запросы
- Найти все
WHERE
, GROUP BY
и предложения ORDER BY` из этих запросов
- Сформулируйте индексы в этом порядке
WHERE
столбцы условия со статическими значениями
GROUP BY
столбцы
ORDER BY
столбцы
- Избегайте полных сканирований таблиц (в запросах отсутствует разумное
WHERE
предложение)
- Избегайте групп с плохими ключами (или, по крайней мере, кешируйте эти группы с плохими ключами)
- Выберите лучший механизм хранения MySQL ( InnoDB или MyISAM ) для таблиц
Я писал об этом 5% -ом практическом правиле в прошлом:
ОБНОВЛЕНИЕ 2012-11-14 13:05 ПО ВОСТОЧНОМУ ВРЕМЕНИ
Я оглянулся на ваш вопрос и на оригинальный пост SO . Затем я подумал о своем, Analysis for InnoDB
я упоминал ранее. Это совпадает с person
таблицей. Почему?
Для обеих таблиц mf
иperson
- Механизм хранения - InnoDB
- Первичный ключ
id
- Доступ к таблице по вторичному индексу
- Если бы таблица была MyISAM, мы бы увидели совершенно другой
EXPLAIN
план
Теперь посмотрим на запрос от SO вопроса: select * from person order by age\G
. Так как WHERE
предложение отсутствует , вы явно потребовали полного сканирования таблицы . Порядок сортировки таблицы по умолчанию будет id
(PRIMARY KEY) из-за его auto_increment, а gen_clust_index (он же Clustered Index) упорядочен по внутреннему rowid . Когда вы упорядочиваетесь по индексу, имейте в виду, что вторичные индексы InnoDB имеют идентификатор строки, присоединенный к каждой записи индекса. Это создает внутреннюю потребность в полном доступе к строке каждый раз.
Настройка ORDER BY
таблицы InnoDB может быть довольно сложной задачей, если вы игнорируете эти факты об организации индексов InnoDB.
Возвращаясь к этому SO-запросу, поскольку вы явно требовали полного сканирования таблицы , IMHO, MySQL Query Optimizer сделал правильную вещь (или, по крайней мере, выбрал путь наименьшего сопротивления). Когда дело доходит до InnoDB и запроса SO, гораздо проще выполнить полное сканирование таблицы, а затем и некоторое, filesort
а не полное сканирование индекса и поиск строки с помощью gen_clust_index для каждой записи вторичного индекса.
Я не сторонник использования Index Hints, потому что он игнорирует план EXPLAIN. Несмотря на это, если вы действительно знаете свои данные лучше, чем InnoDB, вам придется прибегнуть к индексным подсказкам, особенно с запросами, которые не содержат WHERE
оговорок.
ОБНОВЛЕНИЕ 2012-11-14 14:21 ПО ВОСТОЧНОМУ ВРЕМЕНИ
Согласно книге « Понимание внутренних особенностей MySQL»
В параграфе 7 говорится следующее:
Данные хранятся в специальной структуре, называемой кластерным индексом , который представляет собой B-дерево с первичным ключом, действующим в качестве значения ключа, и фактической записью (а не указателем) в части данных. Таким образом, каждая таблица InnoDB должна иметь первичный ключ. Если он не указан, то в качестве первичного ключа добавляется специальный столбец идентификатора строки, который обычно не виден пользователю. Вторичный ключ будет хранить значение первичного ключа, который идентифицирует запись. Код B-дерева можно найти в innobase / btr / btr0btr.c .
Вот почему я говорил ранее: гораздо проще выполнить полное сканирование таблицы и затем некоторую сортировку файлов, чем выполнять полное сканирование индекса и поиск строки с помощью gen_clust_index для каждой записи вторичного индекса . InnoDB будет делать двойной поиск индекса каждый раз . Это звучит жестоко, но это только факты. Опять же, принять во внимание отсутствие WHERE
пункта. Это само по себе является подсказкой оптимизатору запросов MySQL для полного сканирования таблицы.
FOR ORDER BY
(что является конкретным случаем в этом вопросе). В вопросе указывалось, что в этом случае механизм хранения былInnoDB
(и исходный вопрос SO показывает, что строки по 10 КБ довольно равномерно распределены по 8 элементам, здесь также не должно быть проблемы с количеством элементов). К сожалению, я не думаю, что это отвечает на вопрос.filesort
Выбор был решен оптимизатором запросов по одной простой причине: он испытывает недостаток в предвидение данных , которые у вас есть. Если ваш выбор использования индексных подсказок (основанный на проблеме № 2) приносит вам удовлетворительное время выполнения, тогда непременно сделайте это. Ответ, который я дал, был всего лишь академическим упражнением, чтобы показать, насколько темпераментным может быть MySQL Query Optimizer, а также предложить варианты действий.Адаптировано (с разрешения) от ответа Дениса на другой вопрос о SO:
Поскольку все записи (или почти все) будут выбираться запросом, обычно лучше вообще не иметь индекса. Причина этого в том, что чтение индекса на самом деле стоит чего-то.
Поскольку вы работаете со всей таблицей, последовательное чтение таблицы и сортировка ее строк в памяти может быть вашим самым дешевым планом. Если вам нужно всего несколько строк, и большинство из них будет соответствовать предложению where, переход к наименьшему индексу поможет.
Чтобы понять почему, представьте, как работает дисковый ввод-вывод.
Предположим, вам нужна вся таблица без индекса. Для этого вы читаете data_page1, data_page2, data_page3 и т. Д., Посещая различные страницы диска, связанные с порядком, пока не дойдете до конца таблицы. Вы потом сортируете и возвращаете.
Если вы хотите, чтобы верхние 5 строк были без индекса, вы последовательно читали бы всю таблицу, как и раньше, при сортировке кучи верхних 5 строк. По общему признанию, это много чтения и сортировки для нескольких строк.
Предположим теперь, что вам нужна вся таблица с индексом. Для этого вы последовательно читаете index_page1, index_page2 и т. Д. Это затем приводит вас к посещению, скажем, data_page3, затем data_page1, затем data_page3, затем data_page2 и т. Д. В совершенно случайном порядке (то есть, по которому отсортированные строки появляются в данных). Внедрение ввода-вывода делает более дешевым последовательное считывание всего беспорядка и сортировку пакета памяти в памяти.
Если вы просто хотите, чтобы верхние 5 строк индексированной таблицы, напротив, использование индекса стало правильной стратегией. В худшем случае вы загружаете 5 страниц данных в память и идете дальше.
Кстати, хороший планировщик SQL-запросов примет решение о том, использовать индекс или нет, исходя из того, насколько фрагментированы ваши данные. Если выборка строк по порядку означает масштабирование назад и вперед по всей таблице, хороший планировщик может решить, что индекс не стоит использовать. Напротив, если таблица кластеризована с использованием того же индекса, строки гарантированно будут в порядке, что увеличивает вероятность ее использования.
Но затем, если вы объединяете тот же запрос с другой таблицей, и эта другая таблица имеет чрезвычайно избирательное предложение where, которое может использовать небольшой индекс, планировщик может решить, что на самом деле лучше, например, извлечь все идентификаторы строк, помеченных как
foo
хеш присоединяйся к таблицам, и куча сортируй их в памяти.источник