Действительно ли необходимо индексировать все выбранные столбцы, чтобы 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 элементам, здесь также не должно быть проблемы с количеством элементов). К сожалению, я не думаю, что это отвечает на вопрос.