为什么MySQL查询优化器会选择聚集主索引上的二级索引?

时间:2011-11-15 18:04:53

标签: mysql innodb

为什么Mysql优化器在执行'select * from lookup'而没有order by子句时选择二级索引。

这只是一个侥幸,还是这是一个幕后优化,假设你添加了一个二级索引,它比主键更重要。

我希望结果按主键排序,因为扫描所有叶节点可以提供回答此查询所需的所有数据。

要重现我创建一个简单的键/值对表(注意不是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)

4 个答案:

答案 0 :(得分:4)

实际上,聚集索引(又名gen_clust_index)的填充顺序除了以rowid顺序之外没有押韵或理由。几乎不可能按id顺序订购rowid。

在InnoDB中,非聚簇索引(也称为二级索引)中的记录包含不在二级索引中的行的主键列。 InnoDB使用此主键值来搜索聚簇索引中的行。

二级索引管理订单。但是,每个辅助索引条目都有一个指向正确行的主键。另外,请考虑您为k_name提及的覆盖索引方案。

现在,让我们暂时改变一下,讨论PRIMARY KEY和k_name:

问题:原始查询,主键或k_name请求的列数更多?

ANSWER :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 Query Optimizer会选择最能满足您查询的索引。当然,k_name具有不公平的优势,因为

  • 表格中的每一列都单独编入索引
  • 表格中的每一列都是Candidate Key
  • k_name不是SECONDARY INDEX ,因为它是一个候选键,就像PRIMARY KEY一样。
  • 用户定义的聚簇索引一旦建立就不能更改行顺序

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

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)

答案 1 :(得分:1)

那么索引在获取该查询的数据方面同样有效,所以我猜测优化器刚刚退出了“这个就行”

添加另一个唯一索引,可能是因为它们都同样有效,一些“FindBestIndex”例程会随着它读取的最后一个例程而退出。

这不是我期望的行为,但是如果我关心订单,我会按id添加订单,他们让优化器选择主键而不是进行两次传递并进行排序。

答案 2 :(得分:1)

这是因为InnoDB二级索引还包括主键列。因此,MySQL能够直接从二级索引获取所有相关数据,而不会触及数据行,因此可以节省磁盘IO。

参考文献:

答案 3 :(得分:0)

我认为您不理解类型列。类型列'index'表示完整索引扫描。在这种情况下,如果“额外”列具有“使用索引”,则意味着mysql可以从索引获取查询所需的所有数据,并且无需求助于实际的表行。所以这里的引擎,而不是去行(通常是昂贵的),使用索引,而索引具有查询所需的所有数据。辅助索引具有主键(在您的情况下为id)作为数据。也就是说,如果在辅助索引中查找某个键,则会获得表记录的主键。由于您刚刚询问了所有值,因此只需迭代二级索引即可获得所需的值。

如果引擎选择迭代主键,则主键直接导致实际的表行。 Mysql试图避免这种行为,因为它通常效率低下。这是低效的,因为通常行包含的数据多于索引中包含的数据,并且您可能需要执行更多的IO。

http://dev.mysql.com/doc/refman/5.0/en/explain-output.html