【问题标题】:Why covering index not used in the case of meeting the conditions?为什么在满足条件的情况下不使用覆盖索引?
【发布时间】:2020-02-09 12:42:01
【问题描述】:

覆盖索引是 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_namelast_name是索引列,WHERE子句中的emp_no是主键。但是,执行计划显示结果行是从主索引树中检索的。

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

编辑

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

索引:

行数:8204

SQL:

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

结果:

【问题讨论】:

    标签: mysql sql non-clustered-index covering-index


    【解决方案1】:

    您有 2 个索引,emp_no 上的主键(聚集索引)和first_name_last_name 上的辅助(非聚集)索引。

    这就是这些索引的样子:

    现在当您运行以下查询时:

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

    SQL 优化器需要找到所有带有emp_ne &lt; 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_namelast_name 的复合索引...因为该索引按first_name 排序,然后按last_name 排序,它不会有用在last_name 上进行搜索查询。在这种情况下,SQL 优化器将扫描您的整个表以查找带有last_name = 'smith' 的记录


    更新

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

    餐厅索引

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

    酒店索引

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

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

    【讨论】:

    • 我给出了另一个我遇到的例子。你能解释一下原因吗?
    • 您需要意识到的是,索引仅在您搜索时才有用......在第二个示例中,二级索引不包含任何有关 id 的信息,您正在搜索 id ...您希望 MySQL 服务器如何使用该索引来搜索 id?
    • 我认为这两个示例应该得到相同的结果,都使用覆盖索引或都使用主索引。因为在这两个例子中,SELECT 子句中的列是二级索引,而 WHERE 子句中的列是主索引。另外,如你所说,在第二个例子中,二级索引不包含id,所以执行计划的typeindex,即MySQL扫描二级索引,使用id进行过滤。
    • 您可以看到,在这两种情况下,possible_keys 字段都包含您的主索引...所以这意味着 MySQL 优化器知道它可以使用您的主键索引进行搜索...但是您不能强制优化器使用您的索引...例如,假设您的表中只有 5 条记录... SQL 优化器会确定表扫描是比通过索引更快的方法。 SQL 优化器使用启发式方法来决定最佳执行计划...
    猜你喜欢
    • 2021-09-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-05
    • 2013-07-08
    • 1970-01-01
    • 1970-01-01
    • 2012-06-20
    • 2022-11-28
    相关资源
    最近更新 更多