为什么MySQL查询优化器选择辅助索引而不是聚集主索引?

s6fujrry  于 2023-01-08  发布在  Mysql
关注(0)|答案(4)|浏览(123)

为什么Mysql优化器在执行没有order by子句的“select * from lookup”时选择辅助索引。
这只是一个侥幸,还是这是一个幕后优化,假设因为您添加了一个辅助索引,它比主键更重要。
我希望结果按主键排序,因为对所有叶节点的扫描可以提供回答此查询所需的所有数据。
为了重现,我创建了一个简单的键/值对表(注意不是auto_increment)

create table lookup (
id int not null,
primary key (id),
name varchar(25),
unique k_name (name)
) engine=innodb;

以随机非字母顺序插入一些数据

insert into lookup values(1, "Zebra"),(2, "Aardvark"),(3, "Fish"),(4,"Dog"),(5,"Cat"),(6,"Mouse");

查询数据(这是我希望数据按主键顺序返回的地方)

mysql> select * from lookup;
+----+----------+
| id | name     |
+----+----------+
|  2 | Aardvark |
|  5 | Cat      |
|  4 | Dog      |
|  3 | Fish     |
|  6 | Mouse    |
|  1 | Zebra    |
+----+----------+
6 rows in set (0.00 sec)

但实际上并非如此-似乎已经完成了对k_name叶节点的扫描。

mysql> explain select * from lookup;
+----+-------------+--------+-------+---------------+--------+---------+------+------+-------------+
| id | select_type | table  | type  | possible_keys | key    | key_len | ref  | rows | Extra       |
+----+-------------+--------+-------+---------------+--------+---------+------+------+-------------+
|  1 | SIMPLE      | lookup | index | NULL          | k_name | 28      | NULL |    6 | Using index |
+----+-------------+--------+-------+---------------+--------+---------+------+------+-------------+
1 row in set (0.00 sec)

对我来说,这意味着Mysql使用k_name作为覆盖索引来返回数据。如果我删除k_name索引,则数据将按主键顺序返回。如果我添加另一个未索引的列,则数据将按主键顺序返回。
关于我的设置的一些基本信息。

mysql> show table status like 'lookup'\G
*************************** 1. row ***************************
           Name: lookup
         Engine: InnoDB
        Version: 10
     Row_format: Compact
           Rows: 6
 Avg_row_length: 2730
    Data_length: 16384
Max_data_length: 0
   Index_length: 16384
      Data_free: 0
 Auto_increment: NULL
    Create_time: 2011-11-15 10:42:35
    Update_time: NULL
     Check_time: NULL
      Collation: latin1_swedish_ci
       Checksum: NULL
 Create_options:
        Comment:
1 row in set (0.00 sec)

 mysql> select version();
 +------------+
 | version()  |
 +------------+
 | 5.5.15-log |
 +------------+
 1 row in set (0.00 sec)
lztngnrs

lztngnrs1#

实际上,聚集索引(akagen_clustre_index)的填充顺序除了rowid顺序之外没有任何规律或原因。实际上不可能按id顺序对rowid进行排序。
在InnoDB中,非聚集索引(也称为辅助索引)中的记录包含辅助索引中没有的行的主键列。InnoDB使用此主键值在聚集索引中搜索行。
次索引控制顺序。但是,每个次索引条目都有一个指向正确行的主键条目。另外,考虑一下您提到的k_name的覆盖索引场景。
现在,让我们换个主题,讨论一下PRIMARY KEY和k_name:

问题:主键和k_name中,谁的原始查询请求的列更多?
答复:k_name,因为它包含name和id(id是内部的,因为它是PRIMARY KEY)覆盖索引k_name比主键更能满足查询。

现在,如果查询是SELECT * FROM ORDER BY id,您的EXPLAIN PLAN应该如下所示:

mysql> explain select * from lookup order by id;
+----+-------------+--------+-------+---------------+---------+---------+------+------+-------+
| id | select_type | table  | type  | possible_keys | key     | key_len | ref  | rows | Extra |
+----+-------------+--------+-------+---------------+---------+---------+------+------+-------+
|  1 | SIMPLE      | lookup | index | NULL          | PRIMARY | 4       | NULL |    6 |       |
+----+-------------+--------+-------+---------------+---------+---------+------+------+-------+

1 row in set (0.00 sec)

MySQL查询优化器不会指定顺序,而是选择最适合查询的索引。

  • 表中的每一列都是单独索引的
  • 表中的每一列都是候选键
    *k_name不是辅助索引,因为它是候选键,就像主键一样。
  • 用户定义聚集索引一旦建立就不能更改行顺序

您根本无法操纵行的顺序。下面是这一点的证明:

mysql> alter table lookup order by name;
Query OK, 6 rows affected, 1 warning (0.23 sec)
Records: 6  Duplicates: 0  Warnings: 1

mysql> show warnings;
+---------+------+-----------------------------------------------------------------------------------+
| Level   | Code | Message                                                                           |
+---------+------+-----------------------------------------------------------------------------------+
| Warning | 1105 | ORDER BY ignored as there is a user-defined clustered index in the table 'lookup' |
+---------+------+-----------------------------------------------------------------------------------+
1 row in set (0.00 sec)

mysql> alter table lookup order by id;
Query OK, 6 rows affected, 1 warning (0.19 sec)
Records: 6  Duplicates: 0  Warnings: 1

mysql> show warnings;
+---------+------+-----------------------------------------------------------------------------------+
| Level   | Code | Message                                                                           |
+---------+------+-----------------------------------------------------------------------------------+
| Warning | 1105 | ORDER BY ignored as there is a user-defined clustered index in the table 'lookup' |
+---------+------+-----------------------------------------------------------------------------------+
1 row in set (0.00 sec)
qmelpv7a

qmelpv7a2#

这是因为InnoDB二级索引也包含主键列,因此MySQL能够直接从二级索引中获取所有相关数据,而无需接触数据行,因此节省了磁盘IO。
参考文献:

2izufjch

2izufjch3#

两种索引在获取查询数据方面都是同样有效的,所以我猜优化程序只是放弃了“这就行了”
添加另一个唯一索引,可能是因为它们都同样有效,一些“FindBestIndex”例程在读取最后一个索引时退出。
这也不是我所期望的行为,尽管如果我关心顺序,我会添加一个按id排序的命令,让优化器选择主键,而不是进行两次传递和排序。

px9o7tmv

px9o7tmv4#

我想你没有理解类型列。类型列'index'意味着一个完整的索引扫描。当这种情况下,如果'extra'列有'using index',这意味着mysql可以从索引中获得查询所需的所有数据,而不需要求助于实际的表行。所以这里的引擎,而不是去看那些行(这通常代价很高)会使用包含查询所需的所有数据的索引。(id,在你的例子中)作为数据,也就是说,如果你在二级索引中查找一个键,你就得到了表记录的主键,因为你只是要求所有的值,这就足够迭代二级索引来得到你需要的了。
如果引擎选择遍历主键,主键会直接指向实际的表行,Mysql会尽量避免这种行为,因为这样做通常效率很低,因为行包含的数据通常比索引包含的数据多,可能需要执行更多的IO。
http://dev.mysql.com/doc/refman/5.0/en/explain-output.html

相关问题