MySQL中order by的工作原理

本文详细解析了MySQL中`ORDER BY`的工作原理,包括全字段排序和rowid排序的方式,以及如何通过合理利用索引来优化排序性能。介绍了避免排序的策略,如联合索引和覆盖索引的应用。还探讨了随机选取数据的多种方法及其效率对比,为数据库查询性能优化提供参考。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、全字段排序

假设有查询语句:select city, name, age from t where city=‘杭州’ order by name limit 1000,且city上建立索引

MySQL为order by排序开辟的内存称为sort_buffer,如果要排序的数据量小于这块内存,则在内存中进行排序。

在内存中排序过程:
①初始化sort_buffer,放入name, city, age三个字段;
②从索引city找到第一个满足city='杭州’条件的主键id;
③从主键id索引取出整行,并取得name, city, age三个字段的值,存入sort_buffer中;
④从索引city取下一个满足条件的主键id;
⑤重复③和④的操作,直到不满足条件为止;
⑥对sort_buffer中的数据按照name做快速排序;
⑦取排序结果前1000行返回给客户端。

如果要排序的数据量大于排序内存,则需要使用磁盘临时文件进行辅助排序,也就是普通的外部排序。先把所有的数据分成若干份,每份排序后放入磁盘临时文件,最后再对所有的有序临时文件进行归并排序,合成一个有序的大文件。

2、rowid排序

如果查询要返回的字段很多,那么sort_buffer里面要放的字段数会比较多,这样内存中能够放下的行数就会很少,就可能需要分成很多临时文件来辅助排序,导致性能下降。

对于单行太长的情况,MySQL将使用rowid排序。MySQL中用于设置待排序的行数据的长度的参数是max_length_for_sort_data,如果大于这个值,则转而使用rowid排序。

排序过程:
①初始化sort_buffer,只放入两个字段,name和主键id;
②从索引city找到第一个满足city=‘杭州’的主键id;
③从主键索引上取出整行,并取出name和主键id,放入sort_buffer;
④从索引city上取得下一个满足条件的主键id;
⑤重复③和④,直到不满足条件为止;
⑥对sort_buffer中的数据安装name进行排序;
⑦排序完成后的结果取前1000行,再拿着这1000行的主键id回到原表取得city, name, age三个字段值,依次返回给客户端。

可以看出,这种排序方式需要做额外的回表取数据操作。

注意:MySQL实在担心排序内存不够的时候,才会采用rowid排序,这样排序过程中一次可以排序很多行,但是需要回表取数据;如果内存足够,还是会选择全字段排序,即把所要查询的字段全部读入sort_buffer中,排完序直接返回。因此,rowid排序并不会被优先选择。

3、优化order by

合理地利用索引,将可以做到执行order by的时候,竟然可以不用排序,因为索引上的数据已经做好了排序。

联合索引:
如果对city, name建立联合索引,则对于所有city=‘杭州’的索引,name部分是有序的。此时,排序过程变成:
①从‘city, name’索引上取得一个满足city=‘杭州’的主键id;
②到主键索引上取得整行,取city, name, age三个字段值,返回给客户端;
③在‘city, name’索引上取下一个满足条件的主键id;
④重复②和③,直到第1000条记录,或者不满足条件为止。

覆盖索引:
使用‘city, name’的联合索引,每次还是需要再回表取数据,如果再激进一点,不想回表的话,就需要使用覆盖索引,即对’city, name, age’建立联合索引。此时就就可以在索引树上,直接取得所有需要的信息。需要注意的是,索引是有维护代价的,并不是每次都会为了查询而建立联合覆盖索引,需要在两者间进行权衡。

4、如何随机选取三个字段值

方法一:使用order by rand() limit 3
假设单词表有两个字段(主键id和word),现在我们要从单词表中随机地取出三个英语单词。如果使用order by rand()这种方式,需要using temporary和using filesort。将表中每行的word字段值读入内存临时表,同时对word值调用rand()函数生成一个(0, 1)的随机小数,作为该临时表的第二个字段R,然后要按照字段R进行rowid排序。之所以选择时rowid排序,因为此时使用的是内存临时表,回表过程很快,只需要直接访问内存来获取数据,现在优化器会优先考虑用于排序的行越短越好,所以选择rowid排序。接下来,要从内存临时表中取出随机数字段和位置信息(可以把内存临时表看作是数组,位置就是数组的索引)放入sort_buffer中进行排序,排序完成后还要回内存临时表取出所要查询的字段,返回给客户端。

可以看到,使用这种方式查询的代价比较大,因此要尽量避开。

方法二:随机取某1行,取3次
①取整个表的总行数,假设为N;
②取Y1=floor(N*rand()),floor()表示取整;
③再用limit Y1,1取得一行;
④重复②和③,取再取两行Y2和Y3。
扫描的总行数=N+(Y1+1)+(Y2+1)+(Y3+1)。执行代价小于方法一。

方法三:基于方法二的优化
①取整个表的总行数,假设为N;
②取Y1=floor(Nrand()),Y2=floor(Nrand()),Y3=floor(N*rand()),floor()表示取整;
③取Y1、Y2、Y3中最大的数为M,最小的数为N,剩下的数为T;
④执行select * from t limit N, M-N+1;
⑤取出M-N+1行,此时,取出的第一行就是N+1行,第(T-N+1)行就是第T+1行,最后一个为M+1行。
扫描的总行数=N+(M+1),扫描行数少于方法二。

MySQLorder by原理是通过利用索引顺序或者使用rowid排序来实现数据的排序。当查询语句的order by条件和查询的执行计划中所利用的索引索引键完全一致,并且索引访问方式为rang、ref或者index时,MySQL可以直接取得已经排好序的数据,而无需进行实际的排序操作。对于单行数据过长的情况,MySQL将使用rowid排序,即根据行数据的长度进行排序。此外,MySQL为排序开辟了一个内存区域称为sort_buffer,如果要排序的数据量小于这个内存区域的大小,则会在内存中进行排序。总的来说,MySQLorder by原理是根据索引顺序或者行数据的长度来排序数据,并且使用sort_buffer来存储排序的数据。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [Mysqlorder by 的原理](https://blog.youkuaiyun.com/ADi_1998/article/details/106590037)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [MySQLorder by工作原理](https://blog.youkuaiyun.com/Longstar_L/article/details/107372892)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值