MySQL排序原理与MySQL5.6案例分析【转】

本文来自:http://www.cnblogs.com/cchust/p/5304594.html,其中对于本身以为是重点的加了标记,方便本身查阅。更多详细的说明能够看沃趣科技的文章说明。
html

前言
      排序是数据库中的一个基本功能,MySQL也不例外。用户经过Order by语句即能达到将指定的结果集排序的目的,其实不只仅是Order by语句,Group by语句,Distinct语句都会隐含使用排序。本文首先会简单介绍SQL如何利用索引避免排序代价,而后会介绍MySQL实现排序的内部原理,并介绍与排序相关的参数,最后会给出几个“奇怪”排序例子,来谈谈排序一致性问题,并说明产生现象的本质缘由。mysql

1.排序优化与索引使用
      为了优化SQL语句的排序性能,最好的状况是避免排序,合理利用索引是一个不错的方法。由于索引自己也是有序的,若是在须要排序的字段上面创建了合适的索引,那么就能够跳过排序的过程,提升SQL的查询速度。下面我经过一些典型的SQL来讲明哪些SQL能够利用索引减小排序,哪些SQL不能。假设t1表存在索引key1(key_part1,key_part2),key2(key2)算法

a.能够利用索引避免排序的SQLsql

1
2
3
4
SELECT  FROM  t1  ORDER  BY  key_part1,key_part2;
SELECT  FROM  t1  WHERE  key_part1 = constant  ORDER  BY  key_part2;
SELECT  FROM  t1  WHERE  key_part1 > constant  ORDER  BY  key_part1  ASC ;
SELECT  FROM  t1  WHERE  key_part1 = constant1  AND  key_part2 > constant2  ORDER  BY  key_part2;

b.不能利用索引避免排序的SQL数据库

1
2
3
4
5
6
7
8
9
10
11
//排序字段在多个索引中,没法使用索引排序
SELECT  FROM  t1  ORDER  BY  key_part1,key_part2, key2;
 
//排序键顺序与索引中列顺序不一致,没法使用索引排序
SELECT  FROM  t1  ORDER  BY  key_part2, key_part1;
 
//升降序不一致,没法使用索引排序
SELECT  FROM  t1  ORDER  BY  key_part1  DESC , key_part2  ASC ;
 
//key_part1是范围查询,key_part2没法使用索引排序
SELECT  FROM  t1  WHERE  key_part1> constant  ORDER  BY  key_part2;

2.排序实现的算法
      对于不能利用索引避免排序的SQL,数据库不得不本身实现排序功能以知足用户需求,此时SQL的执行计划中会出现“Using filesort”,这里须要注意的是filesort并不意味着就是文件排序,其实也有多是内存排序,这个主要由sort_buffer_size参数与结果集大小肯定MySQL内部实现排序主要有3种方式,常规排序,优化排序和优先队列排序,主要涉及3种排序算法:快速排序、归并排序和堆排序缓存

假设表结构和SQL语句以下:性能

CREATE TABLE t1(id int, col1 varchar(64), col2 varchar(64), col3 varchar(64), PRIMARY KEY(id),key(col1,col2));
SELECT col1,col2,col3 FROM t1 WHERE col1>100 ORDER BY col2;

a.常规排序,双路排序
(1).从表t1中获取知足WHERE条件的记录
(2).对于每条记录,将记录的主键+排序键(id,col2)取出放入sort buffer
(3).若是sort buffer能够存放全部知足条件的(id,col2)对,则进行排序;不然sort buffer满后,进行排序并写到临时文件中。(排序算法采用的是快速排序算法)
(4).若排序中产生了临时文件,须要利用归并排序算法,保证临时文件中记录是有序的
(5).循环执行上述过程,直到全部知足条件的记录所有参与排序
(6).扫描排好序的(id,col2)队,即sort buffer,并利用主键id去取SELECT须要返回的其余列(col1,col2,col3)
(7).将获取的结果集返回给用户。
      从上述流程来看是否使用文件排序主要看sort buffer是否能容下须要排序的(id,col2)的结果集,这个buffer的大小由sort_buffer_size参数控制。此外一次排序还须要两次IO一次是取排序字段(id,col2)到sort buffer中,第二次是经过上面取出的主键id再来取其余所须要返回列(col1,col2,col3),因为返回的结果集是按col2排序,所以id是乱序的,经过乱序的id取(col1,col2,col3)时会产生大量的随机IO。对于第二次IO取MySQL自己会优化,即在取以前先将主键id排序,并放入缓冲区,这个缓存区大小由参数read_rnd_buffer_size控制,而后有序去取记录,将随机IO转为顺序IO
b.优化排序,单路排序,max_length_for_sort_data
     常规排序方式除了排序自己,还须要额外两次IO。优化排序方式相对于常规排序,减小了第二次IO主要区别在于,一次性取出sql中出现的全部字段放入sort buffer中而不是只取排序须要的字段(id,col2)。因为sort buffer中包含了查询须要的全部字段,所以排序完成后能够直接返回,无需二次取数据。这种方式的代价在于,一样大小的sort buffer,能存放的(col1,col2,col3)数目要小于(id,col2),若是sort buffer不够大,可能致使须要写临时文件,形成额外的IO。固然MySQL提供了参数max_length_for_sort_data,只有当排序sql里出现的全部字段小于max_length_for_sort_data时,才能利用优化排序方式,不然只能用常规排序方式。
c.优先队列排序
     为了获得最终的排序结果,咱们都须要将全部知足条件的记录进行排序才能返回。那么相对于优化排序方式,是否还有优化空间呢?5.6版本针对Order by limit M,N语句,在空间层面作了优化,加入了一种新的排序方式--优先队列,这种方式采用堆排序实现。堆排序算法特征正好能够解limit M,N 这类排序的问题,虽然仍然须要全部字段参与排序,可是只须要M+N个元组的sort buffer空间便可,对于M,N很小的场景,基本不会由于sort buffer不够而致使须要临时文件进行归并排序的问题。对于升序,采用大顶堆,最终堆中的元素组成了最小的N个元素,对于降序,采用小顶堆,最终堆中的元素组成了最大的N的元素。测试

3.排序不一致问题优化

案例1:order by no_index limit n在MySQL5.5和5.6中的不一致spa

MySQL从5.5迁移到5.6之后,发现分页出现了重复值(排序字段没有用索引,或则直接是全表扫描),MariaDB已是优化后的方案,和5.6一致。

问题源头:https://bbs.aliyun.com/read/248026.html,解决办法:http://mysql.taobao.org/monthly/2015/06/04/

测试表与数据:

复制代码
create table t1(id int primary key, c1 int, c2 varchar(128));
insert into t1 values(1,1,'a');
insert into t1 values(2,2,'b');
insert into t1 values(3,2,'c');
insert into t1 values(4,2,'d');
insert into t1 values(5,3,'e');
insert into t1 values(6,4,'f');
insert into t1 values(7,5,'g');
复制代码

假设每页3条记录,第一页limit 0,3和第二页limit 3,3查询结果以下:

咱们能够看到 id为4的这条记录竟然同时出如今两次查询中,这明显是不符合预期的,并且在5.5版本中没有这个问题。

使用优先队列排序的目的就是在不能使用索引有序性的时候,若是要排序,而且使用了limit n,那么只须要在排序的过程当中,保留n条记录便可,这样虽然不能解决全部记录都须要排序的开销,可是只须要 sort buffer 少许的内存就能够完成排序,上面已经说明。

之因此MySQL5.6出现了第二页数据重复的问题,是由于使用了优先队列排序,其使用了堆排序的排序方法,而堆排序是一个不稳定的排序方法,也就是相同的值(例子中的值2)可能排序出来的数据和读出来的数据顺序不一致,没法保证排序先后数据位置的一致,因此致使分页重复的现象

为了不这个问题,有几种方法:

①:索引排序字段

利用索引的有序性,在字段添加上索引,就直接按照索引的有序性进行读取并分页,从而能够规避遇到的这个问题。

②:利用多列索引,对于单列相同没法排序的,利用其主键进行排序:

select * from t1 order by c1,id asc limit 0,3;
select * from t1 order by c1,id asc limit 3,3;

案例2:单路排序和双路排序返回结果不同

两个相似的查询语句,除了返回列不一样,其它都相同,但排序的结果不一致

测试表与数据:

复制代码
create table t2(id int primary key, status int, c1 varchar(255),c2 varchar(255),c3 varchar(255),key(c1));
insert into t2 values(7,1,'a',repeat('a',255),repeat('a',255));
insert into t2 values(6,2,'b',repeat('a',255),repeat('a',255));
insert into t2 values(5,2,'c',repeat('a',255),repeat('a',255));
insert into t2 values(4,2,'a',repeat('a',255),repeat('a',255));
insert into t2 values(3,3,'b',repeat('a',255),repeat('a',255));
insert into t2 values(2,4,'c',repeat('a',255),repeat('a',255));
insert into t2 values(1,5,'a',repeat('a',255),repeat('a',255));
复制代码

分别执行SQL语句:

select id,status,c1,c2 from t2 force index(c1) where c1>='b' order by status;
select id,status from t2 force index(c1) where c1>='b' order by status;

执行结果以下:

看看二者的执行计划是否相同

    为了说明问题,由于测试数据很少,确保能走上c1列索引,加了force index的hint。语句经过c1列索引取id,而后去表中捞取返回的列。根据c1列值的大小,记录在c1索引中的相对位置以下:

(c1,id)<===>(b,6),(b,3),(c,5),(c,2),

对应的status值分别为2,3,2,4。从表中取数据并按status排序,则相对位置变为(6,2,b),(5,2,c),(3,3,b),(2,4,c),这就是第二条语句查询返回的结果,那么为何第一条查询语句(6,2,b),(5,2,c)是调换顺序的呢?

这里说明下:
1. Query 语句所取出的字段类型大小总和小于max_length_for_sort_data 。
2. 排序的字段不包含TEXT和BLOB类型。

以前提到的优化排序就能够明白了:因为第一条查询返回的列的字节数超过了max_length_for_sort_data,致使排序采用常规排序,而在这种状况下第二次IO时,MYSQL自己优化会对id排序,将随机IO转为顺序IO,因此返回的先是5,后是6;而第二条查询采用的是优化排序,没有第二次取数据的过程,保持了排序后记录的相对位置,直接在sort buffer里取出。对于第一条语句,若想采用优化排序,咱们将max_length_for_sort_data设置调大便可,好比2048。

4.参考文档

http://dev.mysql.com/doc/refman/5.6/en/order-by-optimization.html

http://mysql.taobao.org/monthly/2015/06/04/

http://ifxoxo.com/mysql_order_by.html

相关文章
相关标签/搜索