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

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

1.排序优化与索引使用
      为了优化SQL语句的排序性能,最好的情况是避免排序,合理利用索引是一个不错的方法。因为索引本身也是有序的,如果在需要排序的字段上面建立了合适的索引,那么就可以跳过排序的过程,提高SQL的查询速度。下面我通过一些典型的SQL来说明哪些SQL可以利用索引减少排序,哪些SQL不能。假设t1表存在索引key1(key_part1,key_part2),key2(key2)

a.可以利用索引避免排序的SQL

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中的不一致

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

MySQL排序原理与MySQL5.6案例分析【转】的更多相关文章

  1. MySQL查询原理及其慢查询优化案例分享(转)

    MySQL凭借着出色的性能.低廉的成本.丰富的资源,已经成为绝大多数互联网公司的首选关系型数据库.虽然性能出色,但所谓“好马配好鞍”,如何能够更 好的使用它,已经成为开发工程师的必修课,我们经常会从职 ...

  2. MySQL触发器的正确使用与案例分析

    以下的文章主要向大家讲述的是MySQL触发器的实际使用详细说明与实际案例分析,同时本文也列举了一些在MySQL触发器的实际式操作中的代码,以下就是文章的详细内容介绍,望大家借鉴. 触发器案例 mysq ...

  3. MySQL排序原理与案例分析

    前言      排序是数据库中的一个基本功能,MySQL也不例外.用户通过Order by语句即能达到将指定的结果集排序的目的,其实不仅仅是Order by语句,Group by语句,Distinct ...

  4. [转]MySQL排序原理与案例分析

    这篇文章非常好,就把他转过来 前言      排序是数据库中的一个基本功能,MySQL也不例外.用户通过Order by语句即能达到将指定的结果集排序的目的,其实不仅仅是Order by语句,Grou ...

  5. (转)MySQL排序原理与案例分析

    前言      排序是数据库中的一个基本功能,MySQL也不例外.用户通过Order by语句即能达到将指定的结果集排序的目的,其实不仅仅是Order by语句,Group by语句,Distinct ...

  6. MySQL的索引单表优化案例分析

    建表 建立本次优化案例中所需的数据库及数据表 CREATE DATABASE db0206; USE db0206; CREATE TABLE `db0206`.`article`( `id` INT ...

  7. MySQL 5.7 GTID OOM bug案例分析 --大量压测后主从不同步

    转载自:http://www.sohu.com/a/231766385_487483 MySQL 5.7是十年内最为经典的版本,这个观点区区已经表示过很多次.然而,经典也是由不断地迭代所打造的传奇.5 ...

  8. 【MySQL】排序原理与案例分析

    前言 排序是数据库中的一个基本功能,MySQL也不例外.用户通过Order by语句即能达到将指定的结果集排序的目的,其实不仅仅是Order by语句,Group by语句,Distinct语句都会隐 ...

  9. 【转载并整理】mysql排序

    由于oracle中有排序函数,可以使用over的语句方便排序,但是mysql中没有 这里碰到几个mysql的概念:用户变量.系统变量.if语句.函数GROUP_CONCAT 1. 可以使用定义变量(@ ...

随机推荐

  1. Mono资源

    摘要 最近看了一部分mono方面的资料,这里整理一下,在这里列一个目录,方便以后用到的时候查找. Mono Mono 是一个由 Xamarin 公司(先前是 Novell,最早为 Ximian)所主持 ...

  2. 如何在editplus中配置ctags?

    首先要说明的是, 在editPlus中的ctags功能确实是没有 vs vim等中的好用. 最主要的原因 是它不能直接在文件中 跳转. 而是要通过一个另外的框来实现, 这就大大的降低了跳转的速度和使用 ...

  3. Latex 数学符号表

  4. Swift语法入门

    正文参考: https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Progra ...

  5. Swift2.1 语法指南——协议

    原档: https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programm ...

  6. JVM内存监控工具 Jvisualvm

    这个工具是官方提供的,直接在JDK工具包下的bin目录找找就可以找到,或者打开cmd直接输入"jvisualvm"即可打开该工具(配置好java环境变量). 需要在catalina ...

  7. [原创]使用java批量修改文件编码(ANSI-->UTF-8)

    从网上下载的项目,有时候.java文件的编码是ANSI.导入到自己的MyEclipse后,查看项目源码的时候,总是乱码. 一个个.java去修改的话, 既麻烦又不现实.所以写了下面这个工具类,进行批量 ...

  8. SSH-Struts第一弹:ActionSupport类

    Action继承了com.opensymphony.xwork2.ActionSupport. package com.candy.login; import com.opensymphony.xwo ...

  9. 160809209_李梦鑫_C语言程序设计实验3 循环结构程序设计

    <C语言程序设计>实验报告 学 号 160809209 姓 名 李梦鑫 专业.班 计科16-2班 学    期 2016-2017 第1学期 指导教师 黄俊莲 吉吉老师 实验地点 C05 ...

  10. 破解Excel保护

    一.录制宏 二.停止录制 三.查看录制 四.点击编辑进入VB编辑环境 五.清空原有的内容,copy以下代码 Public Sub 工作表保护密码破解() Const DBLSPACE As Strin ...