使用MySQL索引扫描进行排序

时间:2014-06-08 09:55:52

标签: mysql sorting indexing

我通过“高性能MySQL”一书研究MySQL索引细节,我无法理解一件事。

如书所述,(第124页使用索引扫描进行排序)

  

MySQL有两种生成有序结果的方法:它可以使用一个文件排序,   或者它可以按顺序扫描索引。

     

通过索引对结果进行排序仅在索引的顺序为时才有效   与ORDER BY子句完全相同,所有列都按顺序排序   相同的方向(上升或下降)。

     

ORDER BY子句也与查询查询具有相同的限制:它   需要形成索引的最左前缀。在所有其他情况下,   MySQL使用filesort。

此外,作者使用MySQL Sakila的示例DataBase给出了一些示例 [http://dev.mysql.com/doc/sakila/en/][1]

第一个例子很好用:

标准Sakila示例数据库中的租赁表具有索引 (rental_date,inventory_id,customer_id):

CREATE TABLE rental (
...
PRIMARY KEY (rental_id),
UNIQUE KEY rental_date (rental_date,inventory_id,customer_id),
KEY idx_fk_inventory_id (inventory_id),
KEY idx_fk_customer_id (customer_id),
KEY idx_fk_staff_id (staff_id),
...
);
  

MySQL使用rental_date索引来为您排序以下查询   从EXPLAIN中缺少一个文件排序可以看出:

> mysql> EXPLAIN SELECT
> rental_id, staff_id FROM sakila.rental
> -> WHERE rental_date = '2005-05-25'
> -> ORDER BY inventory_id, customer_id\G
> *************************** 1. row *************************** 
> type: ref 
> possible_keys: rental_date 
> key: rental_date 
> rows: 1 
> Extra: Using where 
  

这很有效,即使ORDER BY子句本身不是一个   最左边的索引前缀,因为我们指定了相等   索引中第一列的条件。

重要的是要注意:它们在where子句中使用索引列,但在SELECT查询中使用不同的列。

第二个例子以简短的方式显示:

  

以下查询也有效,因为ORDER BY中有两列   是索引的最左前缀:

     

... WHERE rental_date> '2005-05-25'DERDER BY rental_date,inventory_id;

但是在这里你可以得到不同的结果,而不是你的SELECT列内容:

首先使用filesort:

EXPLAIN 
SELECT `rental_id`, `staff_id` FROM `sakila`.`rental`
WHERE `rental_date` > '2005-05-25'
ORDER BY `rental_date`, `inventory_id`;

输入:ALL possible_key:rental_date
key:NULL 额外:使用在哪里;使用filesort

第二种情况,使用索引:

EXPLAIN 
SELECT `rental_id`, `rental_date`, `inventory_id` FROM `sakila`.`rental`
WHERE `rental_date` > '2005-05-25'
ORDER BY `rental_date`, `inventory_id`;

类型:范围 possible_key:rental_date key:rental_date 额外:使用在哪里;使用索引

为什么它以这种奇怪的方式工作?如前所示,第一个示例使用了索引排序,即使在SELECT子句中包含了带有WHERE子句的不同列。

1 个答案:

答案 0 :(得分:0)

在第二个查询中:

SELECT `rental_id`, `rental_date`, `inventory_id` FROM `sakila`.`rental`
WHERE `rental_date` > '2005-05-25'
ORDER BY `rental_date`, `inventory_id`;

MySql直接从索引中检索数据,并且根本不参考该表 请查看索引定义并将其与查询引用的列进行比较:

UNIQUE KEY rental_date (rental_date,inventory_id,customer_id)

索引包含除rental_id之外的查询引用的所有列,但rental_id是主键,除了在其定义中明确给出的列之外的每个索引,始终还包含主键值。 /> 这是此查询的覆盖索引,请参见此处:http://en.wikipedia.org/wiki/Index_%28database%29#Covering_index


但是在第一个查询中:

SELECT `rental_id`, `staff_id` FROM `sakila`.`rental`
WHERE `rental_date` > '2005-05-25'
ORDER BY `rental_date`, `inventory_id`;

staff_id列,未存储在索引中 在这种情况下,MySql必须首先检索与WHERE条件匹配的索引条目,然后对于每个条目必须从表中获取整个记录以获取该条目的缺失staff_id值。


现在请对您的数据库运行此查询并检查结果:

select count(*) As total,
           sum( case when `rental_date` > '2005-05-25' then 1 else 0 end ) As x1,
           sum( case when `rental_date` = '2005-05-25' then 1 else 0 end ) As x0
from rental
;

在我的sakila database副本中,此查询返回以下内容:

+ ---------- + ------- + ------- +
| total      | x1      | x0      |
+ ---------- + ------- + ------- +
| 16044      | 16036   | 0       |
+ ---------- + ------- + ------- +

如您所见,表中几乎所有记录(99.9%)都大于2005-05-25。 在这种情况下,MySql决定不使用索引从表中检索行,但更喜欢将表的全部内容加载到内存中,并在此处进行排序 - 表格相对较小,仅包含16k条记录。 但是,如果还原条件,MySql更喜欢索引访问方法:

EXPLAIN 
SELECT `rental_id`, `staff_id` FROM `sakila`.`rental`
WHERE `rental_date` < '2005-05-25'
ORDER BY `rental_date`, `inventory_id`;
+ ------- + ---------------- + ---------- + --------- + ------------------ + -------- + ------------ + -------- + --------- + ---------- +
| id      | select_type      | table      | type      | possible_keys      | key      | key_len      | ref      | rows      | Extra      |
+ ------- + ---------------- + ---------- + --------- + ------------------ + -------- + ------------ + -------- + --------- + ---------- +
| 1       | SIMPLE           | rental     | range     | rental_date        | rental_date | 5            |          | 8         | Using index condition |
+ ------- + ---------------- + ---------- + --------- + ------------------ + -------- + ------------ + -------- + --------- + ---------- +

为什么在第一种情况下不使用索引?因为使用索引条目从表中检索记录通常是最昂贵的方法 - 真的:) 该索引仅在必须检索表的一小部分的情况下是好的 - 几个百分点,可能&lt; 10%。对于从索引中检索的每个索引记录,MySql必须使用它的主键获取一个记录 - 这是一个随机表访问,这比顺序访问慢几倍。
要通过id从表中只获取一条记录,MySql必须获取包含多条记录的整个页面(块)数据。使用排序索引,我们必须从表中的不同位置逐个获取记录,因此当我们想要使用索引获得90%的表时,会多次检索相同的数据块。在这种情况下,只需按顺序读取一次并在内存中对它们进行排序就更容易,更便宜。