MySql性能(7)—MySql索引扫描与order by排序优
在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. 关键信息提取
-
使用索引扫描来做排序,那么explain的type列为index。但是若需要回表查询,index效率会比ALL效率低,mysql优化器可能会使用ALL类型进行索引访问。
-
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)
- order by子句上索引列不满足最终前缀的例子
比如:school_id、grade_id、score组成唯一索引
比如按照学生成绩排序,在where条件中已经根据school_id和grade_id筛选出某个学校某个年级的学生,order by子句上只有一个score字段,那么得到的数据就是有序的。若是筛选出某个学校好几个年级的学生,order by只有一个score字段,得到的数据集合依旧是无序的。
2. 优化场景
数据在磁盘的存储一般是按照插入的顺序进行存储(也就是按照数据行顺序存储)。如where子句中使用索引,那么一般会按照索引的顺序得到有序的数据。
- 查询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:限制查询结果返回的数量