MySQL为什么选择这个执行计划?

Mat*_*ick 7 mysql innodb performance index query-performance

我有两个疑问,

select some_other_column 
from `table` 
order by primary_index_column asc 
limit 4000000, 10;
Run Code Online (Sandbox Code Playgroud)

select some_other_column 
from `table` 
order by secondary_index_column asc 
limit 4000000, 10;
Run Code Online (Sandbox Code Playgroud)

两者都返回 10 行;第一个需要 2.74 秒,第二个需要 7.07 秒。 some_other_column不是任何索引的一部分。 primary_index_column是主键列;secondary_index_column有一个 b 树索引和 200 的基数(根据 MySQL)。

下面是explain结果:

mysql> explain select some_other_column from `table` order by primary_index_column limit 4000000, 10;
+----+-------------+---------+-------+---------------+---------+---------+------+---------+-------+
| id | select_type | table   | type  | possible_keys | key     | key_len | ref  | rows    | Extra |
+----+-------------+---------+-------+---------------+---------+---------+------+---------+-------+
|  1 | SIMPLE      | table   | index | NULL          | PRIMARY | 4       | NULL | 4000010 |       |
+----+-------------+---------+-------+---------------+---------+---------+------+---------+-------+

mysql> explain select some_other_column from `table` order by secondary_index_column limit 4000000, 10;
+----+-------------+---------+------+---------------+------+---------+------+---------+----------------+
| id | select_type | table   | type | possible_keys | key  | key_len | ref  | rows    | Extra          |
+----+-------------+---------+------+---------------+------+---------+------+---------+----------------+
|  1 | SIMPLE      | table   | ALL  | NULL          | NULL | NULL    | NULL | 4642945 | Using filesort |
+----+-------------+---------+------+---------------+------+---------+------+---------+----------------+
Run Code Online (Sandbox Code Playgroud)

为什么 MySQL 为第二个查询选择特定的执行计划? 我不明白为什么它可以将索引用于第一个查询,但不能用于第二个查询。

Rol*_*DBA 7

InnoDB 中的索引列总是附加一个附加到gen_clust_index(又名聚集索引)的键。这将被第一个查询遍历以按索引顺序到达第 4000000 行。由于它是唯一被请求的列,因此不需要访问该表。

第二个查询必须将表中的非索引列与索引列一起收集到临时表中。然后在将非索引列显示为 SELECT 输出之前对临时表进行排序。

注意另一个对比

  • 表数为 4636881
  • 第一个查询的 EXPLAIN 计划遍历了 4000010 个 indexed_column 键。无需读取最后 636871 个密钥。
  • 第二个查询的 EXPLAIN 计划遍历了按 indexed_column 排序的 4636881 行。对于从表中取出非索引列的每一行,索引列(已按索引排序)被查找并随之而来。tmp 表按索引列排序,然后 mysqld 取消前 4000000 行,留下 10 行。仅针对 10 行的表和索引之间的所有交互都是瓶颈。

常见的事情

在这两种情况下,查询都指定要遍历的行数。由于表中的行数为 4636881,我们应该很容易期待一次完整的扫描。当 MySQL 查询优化器决定在何处执行完整扫描时,对比变得明显。

  • 第一个查询仅在 SELECT 列表和 WHERE 子句中引用索引列。MySQL 查询优化器选择执行完全索引扫描而无需联系表,因为所需的一切都在索引中。
  • 第二个查询引用 WHERE 子句中的索引列。但是,它必须访问表以检索相应的非索引列。MySQL Query Optimizer 被查询提示它不能使用索引,因为它预期读取的行数。作为任何 RDBMS 的经验法则,如果必须读取超过 5% 的表来完成查询,MySQL 查询优化器只会将索引“置于总线下”并进行全表扫描

算一算,这里是 MySQL 查询优化器的计算结果:

  • 4636881 的 5% 是 231844
  • 第二个查询命令读取 4000000 行,远高于 231844
  • MySQL Query Optimizer 意识到表(因为非索引列)和索引(因为索引列)之间会有太多的交互来获取所需的数据。它决定只读取表(因为索引列和非索引列都驻留在表中)而不是在它们之间来回反弹。

老实说,根据表的行数、表的现有索引以及查询规定的行数,MySQL 查询优化器做出了正确的决定

推荐

创建此索引

ALTER TABLE `table` ADD INDEX mynewndx (indexed_column,some_other_column);
Run Code Online (Sandbox Code Playgroud)

并且您的第二个查询将永远不会再次触及该表。当 MySQL 查询优化器看到这个新索引时,它的行为会大不相同。