mysql - 使用 MySQL 索引扫描进行排序

标签 mysql sorting indexing

我通过《高性能MySQL》一书研究了MySQL索引详细信息,但我无法理解一件事。

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

MySQL has two ways to produce ordered results: it can use a filesort, or it can scan an index in order.

Ordering the results by the index works only when the index’s order is exactly the same as the ORDER BY clause and all columns are sorted in the same direction (ascending or descending).

The ORDER BY clause also has the same limitation as lookup queries: it needs to form a leftmost prefix of the index. In all other cases, MySQL uses a filesort.

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

第一个示例运行良好:

标准 Sakila 示例数据库中的租赁表有一个索引 (租赁日期、库存 ID、客户 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 uses the rental_date index to order the following query, as you can see from the lack of a filesort in 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 

This works, even though the ORDER BY clause isn’t itself a leftmost prefix of the index, because we specified an equality condition for the first column in the index.

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

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

The following query also works, because the two columns in the ORDER BY are a left-most prefix of the index:

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

但是在这里您可以获得不同的结果而不是 SELECT 列内容:

第一种情况,使用文件排序:

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

类型:全部 possible_key: 租赁日期
键:空 额外:使用地点;使用文件排序

第二种情况,使用索引:

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: 租赁日期 键:租赁日期 额外:使用地点;使用索引

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

最佳答案

在第二个查询中:

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 数据库 副本中,此查询返回以下内容:

+ ---------- + ------- + ------- +
| 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 |
+ ------- + ---------------- + ---------- + --------- + ------------------ + -------- + ------------ + -------- + --------- + ---------- +

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

关于mysql - 使用 MySQL 索引扫描进行排序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24104953/

相关文章:

java - Jackcess 获取排序表或其中最大索引

mysql - 如果线程丢失,则解锁表

C++ 随机快速排序段错误

mongodb - MongoDB 中的自定义索引比较器

algorithm - 合并排序如何返回值

c# - Windows 窗体计算器算法。排序和最高/最低数字

sql - 如何使用 MySQL 索引列?

php - 复制并添加另一个月到 MySQL 表

mysql - $wpdb->get_results 问题

mysql - 使用 phpmyadmin 将单列 csv 文件导入 mysql