mysql使用索引扫描来做排序

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了mysql使用索引扫描来做排序相关的知识,希望对你有一定的参考价值。

  mysql有两种方式可以生成有序的结果,通过排序操作或者按照索引顺序扫描,如果explain的type列的值为index,则说明mysql使用了索引扫描来做排序(不要和extra列的Using index搞混了,那个是使用了覆盖索引查询)。扫描索引本身是很快的,因为只需要从一条索引记录移动到紧接着的下一条记录,但如果索引不能覆盖查询所需的全部列,那就不得不扫描一条索引记录就回表查询一次对应的整行,这基本上都是随机IO,因此按索引顺序读取数据的速度通常要比顺序地全表扫描慢,尤其是在IO密集型的工作负载时。

  mysql可以使用同一个索引既满足排序,又用于查找行,因此,如果可能,设计索引时应该尽可能地同时满足这两种任务,这样是最好的。只有当索引的列顺序和order by子句的顺序完全一致,并且所有列的排序方向(倒序或升序,创建索引时可以指定ASCDESC)都一样时,mysql才能使用索引来对结果做排序,如果查询需要关联多张表,则只有当order by子句引用的字段全部为第一个表时,才能使用索引做排序,order by子句和查找型查询的限制是一样的,需要满足索引的最左前缀的要求,否则mysql都需要执行排序操作,而无法使用索引排序。

 

示例:

有表rental,表结构如下:

mysql > CREATE TABLE `rental` (
  `rental_id` int(11) NOT NULL AUTO_INCREMENT,
  `rental_date` datetime NOT NULL,
  `inventory_id` mediumint(8) unsigned NOT NULL,
  `customer_id` smallint(5) unsigned NOT NULL,
  `return_date` datetime DEFAULT NULL,
  `staff_id` tinyint(3) unsigned NOT NULL,
  `last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  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`),
  CONSTRAINT `fk_rental_customer` FOREIGN KEY (`customer_id`) REFERENCES `customer` (`customer_id`) ON UPDATE CASCADE,
  CONSTRAINT `fk_rental_inventory` FOREIGN KEY (`inventory_id`) REFERENCES `inventory` (`inventory_id`) ON UPDATE CASCADE,
  CONSTRAINT `fk_rental_staff` FOREIGN KEY (`staff_id`) REFERENCES `staff` (`staff_id`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=16050 DEFAULT CHARSET=utf8;
 
表数据量如下:
mysql > select count(*) from rental;
+----------+
| count(*) |
+----------+
|    16044 |
+----------+
1 row in set (0.04 sec)
 
索引信息如下: 
mysql > show index from rental;
 技术分享

 

mysql可以使用rental_date索引为下面的SQL查询做排序,从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 ***************************

           id: 1

  select_type: SIMPLE

        table: rental

         type: ref

possible_keys: rental_date

          key: rental_date

      key_len: 5

          ref: const

         rows: 1

        Extra: Using where

1 row in set (0.01 sec)

 

注意:where条件列可以不按照索引定义的顺序出现,不管按照什么顺序出现索引列,只要出现的索引列在索引定义顺序的列上能连起来就行,但是order by列不同,出现顺序一定得按照索引定义的顺序,否则无法使用索引进行排序,如,把inventory_idcostomer_id交换一下,就会出现filesort,因为索引是按照定义时的顺序排序,order by列打乱这个排序顺序就无法使用索引进行排序了

mysql > explain select rental_id,staff_id from sakila.rental where rental_date = ‘2005-05-25‘ order by customer_id,inventory_id\G;

*************************** 1. row ***************************

           id: 1

  select_type: SIMPLE

        table: rental

         type: ref

possible_keys: rental_date

          key: rental_date

      key_len: 5

          ref: const

         rows: 1

        Extra: Using where; Using filesort

1 row in set (0.00 sec)

 

从上面示例中可以看到,order by子句不满足索引的最左前缀要求(rental_date列没有出现,只出现了索引的后边两列),不能用rental_date来排序,但可以用于rental_date列查询,这是因为索引的第一个列被指定为常数。

 

还有一些可以使用索引排序的查询示例:

下面这个索引第一列在where上,第二列在order by

mysql > explain select rental_id,staff_id from sakila.rental where rental_date=‘2005-05-05‘ order by inventory_id desc\G;

*************************** 1. row ***************************

           id: 1

  select_type: SIMPLE

        table: rental

         type: ref

possible_keys: rental_date

          key: rental_date

      key_len: 5

          ref: const

         rows: 1

        Extra: Using where

1 row in set (0.00 sec)

 

下面这个查询因为order by使用的两列就是索引的最左前缀

mysql > explain select rental_id,staff_id from sakila.rental where rental_date=‘2005-05-05‘ order by rental_date,inventory_id\G;

*************************** 1. row ***************************

           id: 1

  select_type: SIMPLE

        table: rental

         type: ref

possible_keys: rental_date

          key: rental_date

      key_len: 5

          ref: const

         rows: 1

        Extra: Using where

1 row in set (0.00 sec)

 

下面是一些不能使用索引做排序的查询:

下面这个查询使用了两种不同的排序方向,但是索引列最左前缀是符合的

mysql > explain select rental_id,staff_id from sakila.rental where rental_date=‘2005-05--25‘ rder by inventory_id desc,customer_id asc\G;

*************************** 1. row ***************************

           id: 1

  select_type: SIMPLE

        table: rental

         type: ref

possible_keys: rental_date

          key: rental_date

      key_len: 5

          ref: const

         rows: 1

        Extra: Using where; Using filesort

1 row in set (0.00 sec)

 

下面这个查询的order by子句中使用了一个不在索引中的列

mysql > explain select rental_id,staff_id from sakila.rental where rental_date=‘2005-05--25‘ order by inventory_id,staff_id\G;

*************************** 1. row ***************************

           id: 1

  select_type: SIMPLE

        table: rental

         type: ref

possible_keys: rental_date

          key: rental_date

      key_len: 5

          ref: const

         rows: 1

        Extra: Using where; Using filesort

1 row in set (0.00 sec)

 

下面这个查询的whereorder by中的列无法组合成索引的最左前缀,中间缺失了inventory_id列:

mysql > explain select rental_id,staff_id from sakila.rental where rental_date=‘2005-05-25‘ order by customer_id\G;

*************************** 1. row ***************************

           id: 1

  select_type: SIMPLE

        table: rental

         type: ref

possible_keys: rental_date

          key: rental_date

      key_len: 5

          ref: const

         rows: 1

        Extra: Using where; Using filesort

1 row in set (0.00 sec)

 

下面这个查询在索引列的第一列上是范围查询,所以mysql无法使用索引的其余列,这个范围条件后边的无论是查询条件列还是排序列都无法使用到索引:

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

           id: 1

  select_type: SIMPLE

        table: rental

         type: ALL

possible_keys: rental_date

          key: NULL

      key_len: NULL

          ref: NULL

         rows: 16005

        Extra: Using where; Using filesort

1 row in set (0.00 sec)

 

面这个查询在inventory_id列上有多个等于条件,对于排序来说,这也是一种范围查询,inventory_id条件列后面的无论是查询还是排序都无法使用索引:

mysql > explain select rental_id,staff_id from sakila.rental where rental_date=2005-05-25 and inventory_id in (1,2) order by customer_id\G;

*************************** 1. row ***************************

           id: 1

  select_type: SIMPLE

        table: rental

         type: ref

possible_keys: rental_date,idx_fk_inventory_id

          key: rental_date

      key_len: 5

          ref: const

         rows: 1

        Extra: Using index condition; Using where; Using filesort

1 row in set, 5 warnings (0.00 sec)

 

下面这个查询理论上是可以使用索引进行关联排序的,但是由于优化器在优化时将film_actor表当作关联的第二个表,所以实际上无法使用索引,即排序列不是驱动表的列就无法使用索引排序:

mysql > explain select actor_id,title from sakila.film_actor join sakila.film using(film_id) order by actor_id\G;

*************************** 1. row ***************************

           id: 1

  select_type: SIMPLE

        table: film

         type: index

possible_keys: PRIMARY

          key: idx_title

      key_len: 767

          ref: NULL

         rows: 1000

        Extra: Using index; Using temporary; Using filesort

*************************** 2. row ***************************

           id: 1

  select_type: SIMPLE

        table: film_actor

         type: ref

possible_keys: idx_fk_film_id

          key: idx_fk_film_id

      key_len: 2

          ref: sakila.film.film_id

         rows: 2

        Extra: Using index

2 rows in set (0.00 sec)

 

可以使用straight_join语句指定关联表的顺序:

mysql > explain select actor_id,title from sakila.film_actor as a straight_join sakila.film as b on a.film_id=b.film_id order by actor_id\G;

*************************** 1. row ***************************

           id: 1

  select_type: SIMPLE

        table: a

         type: index

possible_keys: idx_fk_film_id

          key: PRIMARY

      key_len: 4

          ref: NULL

         rows: 5462

        Extra: Using index

*************************** 2. row ***************************

           id: 1

  select_type: SIMPLE

        table: b

         type: eq_ref

possible_keys: PRIMARY

          key: PRIMARY

      key_len: 2

          ref: sakila.a.film_id

         rows: 1

        Extra: NULL

2 rows in set (0.00 sec)

 

  要注意:straight_join属于非标准的语法,在mysql优化器能做出正确选择的时候就尽量不要使用,只有在mysql优化器做出错误的选择时才使用它,straight_join也是一种join关键字,使用straight_join关键字时,如果只有两个表关联要就只使用straight_join就可以了,不需要再去指定join,left join,right join等关键字,否则会报1066表或表别名重复的错误,另外,使用straight_join后指定关联表的关联字段时发现使用using(xx)报语法错,改使用on a.xx=b.xx形式指定就不报错,不知道是不是使用straight_join关键字时不支持using指定关联字段

以上是关于mysql使用索引扫描来做排序的主要内容,如果未能解决你的问题,请参考以下文章

B+树和B/B-树的区别?Mysql为啥用B+树来做索引?

2021-06-07 mysql索引 + 排序

Mysql之order by|group by 排序优化

(译)MySQL 8.0实验室---MySQL中的倒叙索引(Descending Indexes)

Mysql 索引优化 - 2

高性能的索引策略3-使用索引扫描做排序