Почему индекс покрытия не используется в случае выполнения условий?

Покрывающий индекс — это особый случай индекса в InnoDB, когда все необходимые поля для запроса включены в индекс, как упоминалось в этом блоге https://blog.toadworld.com/2017/04/06./ускорение-ваших-запросов-использование-покрывающего-индекса-в-mysql.

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

Версия MySQL: 5.7.27

Пример таблицы:

mysql> SHOW CREATE TABLE employees.employees\G;
*************************** 1. row ***************************
       Table: employees
Create Table: CREATE TABLE `employees` (
  `emp_no` int(11) NOT NULL,
  `birth_date` date NOT NULL,
  `first_name` varchar(14) NOT NULL,
  `last_name` varchar(16) NOT NULL,
  `gender` enum('M','F') NOT NULL,
  `hire_date` date NOT NULL,
  PRIMARY KEY (`emp_no`),
  KEY `first_name_last_name` (`first_name`,`last_name`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1

Ряды: 300024

Индексы:

mysql> SHOW INDEX FROM employees.employees;
+-----------+------------+----------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table     | Non_unique | Key_name             | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-----------+------------+----------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| employees |          0 | PRIMARY              |            1 | emp_no      | A         |      299379 |     NULL | NULL   |      | BTREE      |         |               |
| employees |          1 | first_name_last_name |            1 | first_name  | A         |        1242 |     NULL | NULL   |      | BTREE      |         |               |
| employees |          1 | first_name_last_name |            2 | last_name   | A         |      276690 |     NULL | NULL   |      | BTREE      |         |               |
+-----------+------------+----------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+

mysql> EXPLAIN SELECT first_name, last_name FROM employees.employees WHERE emp_no < '10010';
+----+-------------+-----------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table     | partitions | type  | possible_keys | key     | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-----------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | employees | NULL       | range | PRIMARY       | PRIMARY | 4       | NULL |    9 |   100.00 | Using where |
+----+-------------+-----------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

Как видно, first_name и last_name в предложении SELECT являются индексированными столбцами, а emp_no в предложении WHERE — первичным ключом. Но план выполнения показывает, что строки результатов извлекаются из первичного индексного дерева.

На мой взгляд, он должен сканировать дерево вторичных индексов и фильтровать результаты по emp_no < '10010', в котором используется покрывающий индекс.

Изменить

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

Индексы: введите здесь описание изображения

Строки:8204

SQL:

explain select poi_id , ctime from another_table where id < 1000;

Результат: введите здесь описание изображения


person 张拉拉    schedule 12.10.2019    source источник


Ответы (1)


У вас есть 2 индекса: первичный ключ (кластеризованный индекс) на emp_no и вторичный (некластеризованный) индекс на first_name_last_name.

Вот как выглядят эти индексы:

введите здесь описание изображения

Теперь, когда вы запускаете следующий запрос:

SELECT first_name, last_name FROM employees.employees WHERE emp_no < '10010';

Оптимизатору SQL необходимо найти все записи с emp_ne < 10010. Ваш индекс first_name_last_name не помогает найти записи с emp_no меньше 10010, потому что он не содержит этой информации.

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

Теперь, если вы измените запрос на:

SELECT * FROM employees.employees WHERE first_name = 'john';

Затем оптимизатор SQL будет использовать ваш вторичный (некластеризованный) индекс для поиска записей, потому что это самый простой способ сузить результаты поиска.

Примечание.

Если вы выполните следующий запрос:

SELECT * FROM employees.employees WHERE last_name = 'smith';

Ваш вторичный индекс не будет использоваться, потому что ваш вторичный индекс представляет собой составной индекс, содержащий first_name и last_name... поскольку индекс сортируется по first_name, а затем по last_name, он не будет полезен для поискового запроса по last_name. В этом случае оптимизатор SQL просканирует всю таблицу, чтобы найти записи с last_name = 'smith'.


Обновлять

Думайте об этом как о указателе в конце книги. Представьте, что у вас есть путеводитель по Бразилии... в нем есть указатель всех ресторанов и еще один указатель всех отелей Бразилии.

Указатель ресторанов

  • Ресторан 1: упоминается на страницах 12 и 77 путеводителя по Бразилии
  • Ресторан 2: упоминается на странице 33 путеводителя по Бразилии
  • ...

Индекс отелей

  • Отель 1: упоминается на странице 5 путеводителя по Бразилии
  • Отель 2: упоминается на страницах 33 и 39 путеводителя по Бразилии
  • ...

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

person Hooman Bahreini    schedule 12.10.2019
comment
Я привел еще один пример, с которым столкнулся. Не могли бы вы объяснить, почему? - person 张拉拉; 12.10.2019
comment
Что вам нужно понять, так это то, что индекс полезен только тогда, когда вы ищете по нему... во втором примере вторичный индекс не содержит никакой информации об идентификаторе, и вы ищете по идентификатору... как вы ожидаете MySQL сервер использовать этот индекс для поиска идентификатора? - person Hooman Bahreini; 12.10.2019
comment
Я думаю, что два примера должны дать одинаковый результат, оба используют покрывающий индекс или оба используют первичный индекс. Потому что в двух примерах столбцы в предложении SELECT являются вторичным индексом, а столбцы в предложении WHERE — первичным индексом. Кроме того, как вы сказали, во втором примере вторичный индекс не включает идентификатор, поэтому type плана выполнения равен index, что означает, что MySQL сканирует вторичный индекс и использует id для фильтрации. - person 张拉拉; 12.10.2019
comment
Вы можете видеть, что в обоих случаях поле possible_keys содержит ваш ОСНОВНОЙ индекс... так что это означает, что оптимизатор MySQL знает, что он может использовать ваш индекс первичного ключа для поиска... но вы не можете заставить оптимизатор использовать ваш индекс... например, представьте, что в вашей таблице всего 5 записей... Оптимизатор SQL решит, что сканирование таблицы — это более быстрый способ, чем просмотр индекса. Оптимизатор SQL использует эвристику для выбора наилучшего плана выполнения... - person Hooman Bahreini; 12.10.2019