4

覆盖索引是 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)

可以看出,SELECT 子句中的first_nameandlast_name是索引列,而emp_noWHERE 子句中的 是主键。但是,执行计划显示结果行是从主索引树中检索的。

在我看来,它应该扫描二级索引树,并过滤结果emp_no < '10010',其中使用了覆盖索引。

编辑

此外,我看到在 MySQL 5.7.21 下同样的情况下使用了覆盖索引。

索引: 在此处输入图像描述

行数:8204

SQL:

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

结果: 在此处输入图像描述

4

1 回答 1

3

您有 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_nameand last_name... 因为该索引按first_namethen排序,last_name因此对于last_name. 在这种情况下,SQL 优化器会扫描你的整个表来查找记录last_name = 'smith'


更新

把它想象成一本书末尾的索引。想象一下,你有一本巴西的旅游指南……它有一个巴西所有餐馆的索引和另一个巴西所有酒店的索引。

餐厅索引

  • 餐厅 1:在巴西指南的第 12 页和第 77 页中提到
  • 餐厅 2:在巴西指南第 33 页提到
  • ...

酒店索引

  • 酒店 1:在巴西指南的第 5 页提到
  • 酒店 2:在巴西指南的第 33 和 39 页中提到
  • ...

现在,如果你想搜索这本书并找到所有提到里约热内卢市的页面,这些索引都没有用。除非这本书有关于城市名称的第三个索引,否则您必须扫描整本书才能找到这些页面。

于 2019-10-12T04:23:02.530 回答