Почему индекс покрытия не используется в случае соблюдения условий? - PullRequest
3 голосов
/ 12 октября 2019

Покрывающий индекс - это особый случай индекса в InnoDB, где все обязательные поля для запроса включены в индекс, как упомянуто в этом блоге https://blog.toadworld.com/2017/04/06/speed-up-your-queries-using-the-covering-index-in-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.

Индексы: enter image description here

Строки: 8204

SQL:

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

Результат: enter image description here

1 Ответ

3 голосов
/ 12 октября 2019

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

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

enter image description here

Теперь при выполнении следующего запроса:

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 путеводителя по Бразилии
  • ...

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

...