数据库

MySql性能(7)—MySql索引扫描与order by排序优

2020-03-29  本文已影响0人  小胖学编程

在mysql中,order by子句也可以使用索引优化。

在《高性能mysql第三版》中关于索引建议是这样描述的:

使用索引扫描来做排序:

MySQL有两种方式可以生成有序的结果:通过排序操作;或者按索引顺序扫描。如果explain出来的type列的值为“index”,则说明MySQL使用了索引扫描来做排序(不要和Extra列的“Using index”搞混淆)。

扫描索引本身是很快的,因为只需要从一条索引记录移动到紧接着的下一条记录。但如果索引不能覆盖查询所需的全部列,那么就不得不每扫描一条记录都要回表查询一次对应的行,着基本是随机IO,因此按索引顺序读取数据的速度通常要比顺序地全表扫描慢,尤其是在IO密集型工作负载时。

只有当索引的列顺序和order by子句的顺序完全一致,并且所有列的排序方向(倒序或正序)都一样时,Mysql才能使用索引来对结果做排序。如果查询需要关联多个表,则只有当Order by子句引用的字段全部为第一个表时,才能使用索引做排序。Order by子句和查找型查询的限制是一样的:需要满足索引的最左前缀要求,否则mysql都需要执行排序操作,而无法利用索引排序。

有一种情况下Order by子句可以不满足索引的最左前缀的要求,就是前导列为常量时,如果where子句或join子句对这些列指定列常量,就可以“弥补”索引的不足。


1. 关键信息提取

  1. 使用索引扫描来做排序,那么explain的type列为index。但是若需要回表查询,index效率会比ALL效率低,mysql优化器可能会使用ALL类型进行索引访问。

  2. order by子句也可以使用索引,也需要满足最左前缀的要求。where子句和order by子句搭配使用时,若where或join语句中含有最左前缀的前导列,那么order by子句也可以与where/join子句共同组成最左前缀。

user表数据量是百万级,name列存在普通索引。可以看到select *时,mysql采用的使用ALL进行索引访问,并且可以看到Extra列上使用Using filesort进行排序。

mysql> explain select id from user order by name;
+----+-------------+-------+------------+-------+---------------+-----------+---------+------+--------+----------+-------------+
| id | select_type | table | partitions | type  | possible_keys | key       | key_len | ref  | rows   | filtered | Extra       |
+----+-------------+-------+------------+-------+---------------+-----------+---------+------+--------+----------+-------------+
|  1 | SIMPLE      | user  | NULL       | index | NULL          | user_name | 767     | NULL | 998179 |   100.00 | Using index |
+----+-------------+-------+------------+-------+---------------+-----------+---------+------+--------+----------+-------------+
1 row in set (0.00 sec)
 
mysql> explain select * from user order by name;
+----+-------------+-------+------------+------+---------------+------+---------+------+--------+----------+----------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows   | filtered | Extra          |
+----+-------------+-------+------------+------+---------------+------+---------+------+--------+----------+----------------+
|  1 | SIMPLE      | user  | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 998179 |   100.00 | Using filesort |
+----+-------------+-------+------------+------+---------------+------+---------+------+--------+----------+----------------+
1 row in set (0.00 sec)
  1. order by子句上索引列不满足最终前缀的例子

比如:school_id、grade_id、score组成唯一索引

比如按照学生成绩排序,在where条件中已经根据school_id和grade_id筛选出某个学校某个年级的学生,order by子句上只有一个score字段,那么得到的数据就是有序的。若是筛选出某个学校好几个年级的学生,order by只有一个score字段,得到的数据集合依旧是无序的。

2. 优化场景

数据在磁盘的存储一般是按照插入的顺序进行存储(也就是按照数据行顺序存储)。如where子句中使用索引,那么一般会按照索引的顺序得到有序的数据。

  1. 查询001学校和002学校,7年级学生的信息,按成绩倒序排列:
select * from table where school_id in ("001","002")  and  grade_id=7 order by score desc;

注意:此处排序不会优化。

select * from table where school_id in ("001","002") and grade_id=7 order by school_id grade_id score desc;

本质上这样排序才能借助到索引扫描,从而优化性能,但是若school_id、grade_id指定一个确定值时,可以仅仅在order by上使用score字段(school_id、grade_id省略)。school_id若是存在多个值时,若order by仅考虑score字段,那么结果集依旧是依旧是无序的。

3. 排疑

order by子句索引列会影响where子句上的索引列最左前缀吗?

如果有这么一条sql:

select * from table where a=1 and b>10 order by c;

如果想在abc三列上去建立一个复合索引,那么如何建立???

在acb列上建立索引? ? ?

不推荐这样建立索引,这样的情况相当于在ac列上建立索引!!!

实际上推荐的是在ab上建立索引。

mysql语句的各个子句中。where子句是筛选数据,order by子句是排序数据。order by排序where子句筛选后的数据集,若where子句使用的是索引访问类型(explain的type列)为index或以上的类型,那么数据集是以索引的顺序的有序集合。若order by筛选的顺序正好是索引的顺序,那么才能优化索引。

但where语句和order by语句处理数据时机不同。order by的索引最左前缀和where子句最左前缀不能相互影响。即acb复合索引中:where走ab索引,order去走c索引的情况是不存在的。

即若建立acb索引,那么where会使用a索引进行筛选,因为where不存在c列条件,那么不能使用b列进行筛选,该索引等效于ac列。

附录

mysql语句的执行顺序:

from:需要从哪个数据表检索数据
join:联合多表查询返回记录时,并生成一张临时表
on:在生成临时表时使用的条件
where:过滤表中数据的条件
group by:如何将上面过滤出的数据分组
having:对上面已经分组的数据进行过滤的条件
select:查看结果集中的哪个列,或列的计算结果
order by :按照什么样的顺序来查看返回的数据
limit:限制查询结果返回的数量

历史文章

mybatis&&数据库优化&&缓存目录

文章参考

mysql中数据存储的顺序是什么?

MySQL语句执行顺序和编写顺序

上一篇下一篇

猜你喜欢

热点阅读