原文链接:https://www.cnblogs.com/moss_tan_jun/p/6021822.html

https://blog.csdn.net/lb517290079/article/details/84164962

为了优化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。

增大 sort_buffer_size 参数设

这个值如果过小的话,再加上你一次返回的条数过多,那么很可能就会分很多次进行排序,然后最后将每次的排序结果再串联起来,这样就会更慢,增大 sort_buffer_size 并不是为了让 MySQL选择改进版的排序算法,而是为了让MySQL尽量减少在排序过程中对须要排序的数据进行分段,因为分段会造成 MySQL 不得不使用临时表来进行交换排序。
但是这个值不是越大越好:
1 Sort_Buffer_Size 是一个connection级参数,在每个connection第一次需要使用这个buffer的时候,一次性分配设置的内存。
2 Sort_Buffer_Size 并不是越大越好,由于是connection级的参数,过大的设置+高并发可能会耗尽系统内存资源。
3 据说Sort_Buffer_Size 超过2M的时候,就会使用mmap() 而不是 malloc() 来进行内存分配,导致效率降低。

这段时间mysql 数据库的性能明显降低,iowait达到了30, 响应时间明显变长.  通过show processlist 查看,发现有很多session在处理sort 操作, 跟DBA一起调试优化,增大sort_buffer_size 好象效果也不大, 通过查看监控,也没发现有硬盘排序. 我怀疑是sort导致性能下降,固让开发修改程序, sort由程序来处理. 星期五发布后,今天发现压力固然好了很多.

因此基本上能确定是sort引起的问题. 今天仔细分析问题,查看mysql的参数时,看到一个叫做max_length_for_sort_data 的参数, 值是1024 仔细查看mysql 的filesort算法时, 发现mysql的filesort有两个方法,MySQL 4.1之前是使用方法A, 之后版本会使用改进的算法B, 但使用方法B的前提是列长度的值小于max_length_for_sort_data, 但我们系统中的列的长度的值会大于1024. 因此也就是说在sort的时候, 是在使用方法A, 而方法A的性能比较差, 也就解释了我们的mysql系统在有sort时,性能差,去掉之后性能马上提高很多的原因.

马上修改max_length_for_sort_data这个值,增大到8096, 果然性能就提高了

Mysql 排序优化的更多相关文章

  1. Mysql 排序优化与索引使用(转)

    为了优化SQL语句的排序性能,最好的情况是避免排序,合理利用索引是一个不错的方法.因为索引本身也是有序的,如果在需要排序的字段上面建立了合适的索引,那么就可以跳过排序的过程,提高SQL的查询速度.下面 ...

  2. Mysql性能优化一

    下一篇:Mysql性能优化二 mysql的性能优化无法一蹴而就,必须一步一步慢慢来,从各个方面进行优化,最终性能就会有大的提升. Mysql数据库的优化技术 对mysql优化是一个综合性的技术,主要包 ...

  3. 关于MySQL数据库优化的部分整理

    在之前我写过一篇关于这个方面的文章 <[原创]为什么使用数据索引能提高效率?(本文针对mysql进行概述)(更新)> 这次,主要侧重点讲下两种常用存储引擎. 我们一般从两个方面进行MySQ ...

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

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

  5. [MySQL性能优化系列]巧用索引

    1. 普通青年的索引使用方式 假设我们有一个用户表 tb_user,内容如下: name age sex jack 22 男 rose 21 女 tom 20 男 ... ... ... 执行SQL语 ...

  6. MySQL性能优化:索引

    MySQL性能优化:索引 索引提供指向存储在表的指定列中的数据值的指针,然后根据您指定的排序顺序对这些指针排序.数据库使用索引以找到特定值,然后顺指针找到包含该值的行.这样可以使对应于表的SQL语句执 ...

  7. mysql 性能优化方向

    国内私募机构九鼎控股打造APP,来就送 20元现金领取地址:http://jdb.jiudingcapital.com/phone.html内部邀请码:C8E245J (不写邀请码,没有现金送)国内私 ...

  8. MySQL性能优化总结

    一.MySQL的主要适用场景 1.Web网站系统 2.日志记录系统 3.数据仓库系统 4.嵌入式系统 二.MySQL架构图: 三.MySQL存储引擎概述 1)MyISAM存储引擎 MyISAM存储引擎 ...

  9. MYSQL性能优化的最佳20+条经验

    MYSQL性能优化的最佳20+条经验 2009年11月27日 陈皓 评论 148 条评论  131,702 人阅读 今天,数据库的操作越来越成为整个应用的性能瓶颈了,这点对于Web应用尤其明显.关于数 ...

随机推荐

  1. C#中的Byte,String,Int,Hex之间的转换函数。

    /// <summary> Convert a string of hex digits (ex: E4 CA B2) to a byte array. </summary> ...

  2. spaceclaim脚本(线生成面体)

    #新建一个列表,用来保存修剪曲线(PS:修建曲线的意思是开始点和结束点不在一起,圆就不属于修建曲线) #注意和Line,Circle类型等的区别 curves = List[ITrimmedCurve ...

  3. 360杯复赛流量分析题 详细writeup

    题目名: 这是捕获的黑客攻击数据包 通过分析流量包,得知黑客先上传了一个文件: 追踪TCP流,可以看到文件内容,是一个木马: 然后通过get请求一个加密key,在响应里能看到key的值. 接下来就是用 ...

  4. SDN上机第五次作业

    2019 SDN上机第五次作业 1.浏览RYU官网学习RYU控制器的安装和RYU开发入门教程,提交你对于教程代码的理解,包括但不限于: 1.1描述官方教程实现了一个什么样的交换机功能? 答:官方教程实 ...

  5. Thingsboard开发环境搭建及编译项目

    第一部分:JDK 1,下载JDK, JDK地址:https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133 ...

  6. php手记之06-tp5验证器

    # 创建验证器 php think make:validate 模块名/验证器名(首字母大写) # 验证器 namespace app\index\validate; use think\Valida ...

  7. Django,Flask,Tornado三大框架对比,Python几种主流框架,13个Python web框架比较,2018年Python web五大主流框架

    Django 与 Tornado 各自的优缺点Django优点: 大和全(重量级框架)自带orm,template,view 需要的功能也可以去找第三方的app注重高效开发全自动化的管理后台(只需要使 ...

  8. 课程学习 - 人类疾病导论 | Introduction To Human Disease

    完美人类假设:一类人,具有最完美的基因组,享受最健康的环境和饮食,同时拥有最健康的思想情绪,最终以最长的寿命,自然死亡. 自然死亡是自然生命最终的归宿,这是写在目前基因组里的铁律! 不管科技如何发展, ...

  9. Vue实现图片预加载

    <script>export default { data () { return { count: 0, } }, mounted: function() { this.preload( ...

  10. ansible常用的方法小结

    一.批量安装zabbix客户端 .拷贝sh脚本和.conf到远程服务器(也可以全量拷贝客户端) ansible all -m copy -a "src=/usr/local/zabbix_a ...