浅谈MySQL排序原理与案例分析
前言 排序是数据库中的一个基本功能,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 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 //排序字段在多个索引中,无法使用索引排序 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.常规排序 3.排序不一致问题 案例1 Mysql从5.5迁移到5.6以后,发现分页出现了重复值。 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,'c'); insert into t1 values(4,'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版本中没有这个问题。产生这个现象的原因就是5.6针对limit M,N的语句采用了优先队列,而优先队列采用堆实现,比如上述的例子order by c1 asc limit 0,3 需要采用大小为3的大顶堆;limit 3,3需要采用大小为6的大顶堆。由于c1为2的记录有3条,而堆排序是非稳定的(对于相同的key值,无法保证排序后与排序前的位置一致),所以导致分页重复的现象。为了避免这个问题,我们可以在排序中加上唯一值,比如主键id,这样由于id是唯一的,确保参与排序的key值不相同。将SQL写成如下: 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,'a',repeat('a',255),255)); insert into t2 values(6,'b',255)); insert into t2 values(5,'c',255)); insert into t2 values(4,255)); insert into t2 values(3,255)); insert into t2 values(2,255)); insert into t2 values(1,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; 执行结果如下: 看看两者的执行计划是否相同 为了说明问题,我在语句中加了force index的hint,确保能走上c1列索引。语句通过c1列索引捞取id,然后去表中捞取返回的列。根据c1列值的大小,记录在c1索引中的相对位置如下: (c1,id)===(b,6),(b,3),(5,c),(c,2),对应的status值分别为2 3 2 4。从表中捞取数据并按status排序,则相对位置变为(6,b),(3,(2,c),这就是第二条语句查询返回的结果,那么为什么第一条查询语句(6,c)是调换顺序的呢?这里要看我之前提到的a.常规排序和b.优化排序中标红的部分,就可以明白原因了。由于第一条查询返回的列的字节数超过了max_length_for_sort_data,导致排序采用的是常规排序,而在这种情况下MYSQL将rowid排序,将随机IO转为顺序IO,所以返回的是5在前,6在后;而第二条查询采用的是优化排序,没有第二次捞取数据的过程,保持了排序后记录的相对位置。对于第一条语句,若想采用优化排序,我们将max_length_for_sort_data设置调大即可,比如2048。 下面是本人关于mysql 自定义排序(field,INSTR,locate)的一点心得,希望对大家有所帮助 原表: id user pass aaa aaa bbb bbb ccc ccc ddd ddd eee eee fff fff 下面是我执行后的结果: SELECT * FROM `user` order by field(2,id) asc id user pass aaa aaa ccc ccc ddd ddd eee eee fff fff bbb bbb 根据结果分析:order by field(2,6) 结果显示顺序为:1 3 4 5 6 2 SELECT * FROM `user` order by field(2,id) desc id user pass bbb bbb aaa aaa ccc ccc ddd ddd eee eee fff fff 根据结果分析:order by field(2,6) 结果显示顺序为:2 1 3 4 5 6 SELECT * FROM `user` ORDER BY INSTR( '2,4',id ) ASC id user pass aaa aaa fff fff bbb bbb ccc ccc eee eee ddd ddd 根据结果分析:order by INSTR(2,6) 结果显示顺序为:1 6 2 3 5 4 SELECT * FROM `user` ORDER BY INSTR( '2,id ) DESC id user pass ddd ddd eee eee ccc ccc bbb bbb aaa aaa fff fff 根据结果分析:order by INSTR(2,6) 结果显示顺序为:4 5 3 2 1 6 SELECT * FROM `user` ORDER BY locate( id,'2,4' ) ASC id user pass aaa aaa fff fff bbb bbb ccc ccc eee eee ddd ddd 根据结果分析:order by locate(2,6) 结果显示顺序为:1 6 2 3 5 4 SELECT * FROM `user` ORDER BY locate( id,4' ) DESC id user pass ddd ddd eee eee ccc ccc bbb bbb aaa aaa fff fff 根据结果分析:order by locate(2,6) 结果显示顺序为:4 5 3 2 1 6 id user pass bbb bbb ccc ccc eee eee ddd ddd aaa aaa fff fff (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
- sql – Liquibase:如何设置UTC格式的“now”的日期列的默认
- PowerShell使用** DacServices **和SQLCMD变量来部署DACPAC
- 使用Linq to SQL进行多线程处理
- sqlserver事务与回滚
- SQL Server – 缺少NATURAL JOIN / x JOIN y USING(字段)
- sql – openrowset for excel:我们可以跳过几行吗?
- sql-server-2008 – 没有Microsoft.SqlServer.ManagedDTS.d
- sql-server – 如何从我的主机Macbook连接到VirtualBox上运
- SqlServer中Sql语句的逻辑执行顺序
- PICT工具