MySQL45讲--order by

样例SQL
select city,name,age from t where city='杭州' order by name limit 1000  ;
1. 全字段排序
因为查询条件是 city,显然在city上加索引
explain sql语句,拿到结果:

 

 

Extra 这个字段中的“Using filesort”表示的就是需要排序,MySQL 会给每个线程分配一块内存用于排序,称为 sort_buffer。
 
详细的执行过程:
1.初始化 sort_buffer,确定放入 name、city、age 这三个字段;
2.从索引 city 找到第一个满足 city='杭州’条件的主键 id;
3.到主键 id 索引取出整行,取 name、city、age 三个字段的值,存入 sort_buffer 中;
4.从索引 city 取下一个记录的主键 id;
5.重复步骤 3、4 直到 city 的值不满足查询条件为止,对应的主键 id 也就是图中的 ID_Y;
6.对 sort_buffer 中的数据按照字段 name 做快速排序;按照排序结果取前 1000 行返回给客户端。

 

 

图中“按 name 排序”这个动作,可能在内存中完成,也可能需要使用外部排序,这取决于排序所需的内存和参数 sort_buffer_size。
 
sort_buffer_size,就是 MySQL 为排序开辟的内存(sort_buffer)的大小。如果要排序的数据量小于 sort_buffer_size,排序就在内存中完成。但如果排序数据量太大,内存放不下,则不得不利用磁盘临时文件辅助排序。
 
2.rowid排序
如果查询要返回的字段很多的话,那么 sort_buffer 里面要放的字段数太多,这样内存里能够同时放下的行数很少,要分成很多个临时文件,排序的性能会很差。所以如果单行里有很多值,方法1效率不够好。
此时,MySQL会采用rowid排序。
回表查询,只拿需要排序的字段name和主键id。
排序后,再回表一次,把所有需要的字段拿全。

 

 

 
总结:
如果 MySQL 实在是担心排序内存太小,会影响排序效率,才会采用 rowid 排序算法,这样排序过程中一次可以排序更多行,但是需要再回到原表去取数据。
 
如果 MySQL 认为内存足够大,会优先选择全字段排序,把需要的字段都放到 sort_buffer 中,这样排序后就会直接从内存里面返回查询结果了,不用再回到原表去取数据。
 
这也就体现了 MySQL 的一个设计思想:如果内存够,就要多利用内存,尽量减少磁盘访问。
 
3.不用order by---建立联合索引
如果能够保证从 city 这个索引上取出来的行,天然就是按照 name 递增排序的话,就可以不用再排序了呢?确实是这样的。
创建一个 city 和 name 的联合索引
alter table t add index city_user(city, name);
只要 city 的值是杭州,name 的值就一定是有序的。
 
整个查询过程的流程就变成了:
从索引 (city,name) 找到第一个满足 city='杭州’条件的主键 id;
到主键 id 索引取出整行,取 name、city、age 三个字段的值,作为结果集的一部分直接返回;
从索引 (city,name) 取下一个记录主键 id;
重复步骤 2、3,直到查到第 1000 条记录,或者是不满足 city='杭州’条件时循环结束。
 
再次优化: 使用 “覆盖索引”
alter table t add index city_user_age(city, name, age);
 
从索引 (city,name,age) 找到第一个满足 city='杭州’条件的记录,取出其中的 city、name 和 age 这三个字段的值,作为结果集的一部分直接返回;
从索引 (city,name,age) 取下一个记录,同样取出这三个字段的值,作为结果集的一部分直接返回;
重复执行步骤 2,直到查到第 1000 条记录,或者是不满足 city='杭州’条件时循环结束。
 
 
4.留给你的问题
select * from t where city in (“杭州”," 苏州 ") order by name limit 100; 
这个 SQL 语句是否需要排序?有什么方案可以避免排序?
回答:
虽然有 (city,name) 联合索引,对于单个 city 内部,name 是递增的。但是由于这条 SQL 语句不是要单独地查一个 city 的值,而是同时查了"杭州"和" 苏州 "两个城市,因此所有满足条件的 name 就不是递增的了。也就是说,这条 SQL 语句需要排序。
 
那怎么避免排序呢?
这里,我们要用到 (city,name) 联合索引的特性,把这一条语句拆成两条语句,
执行流程如下:
select * from t where city=“杭州” order by name limit 100; 这个语句是不需要排序的,客户端用一个长度为 100 的内存数组 A 保存结果。
 select * from t where city=“苏州” order by name limit 100; 得到数组B
 
现在 A 和 B 是两个有序数组,然后你可以用归并排序的思想,得到 name 最小的前 100 值,就是我们需要的结果了。
 
如何实现跨页查找呢?
如果把这条 SQL 语句里“limit 100”改成“limit 10000,100”的话
处理方式:
select * from t where city="杭州" order by name limit 10100; 
select * from t where city="苏州" order by name limit 10100。这时候数据量较大,可以同时起两个连接一行行读结果,用归并排序算法拿到这两个结果集里,按顺序取第 10001~10100 的 name 值,就是需要的结果了。当然这个方案有一个明显的损失,就是从数据库返回给客户端的数据量变大了。所以,如果数据的单行比较大的话,可以考虑把这两条 SQL 语句改成下面这种写法:
select id,name from t where city="杭州" order by name limit 10100; 
select id,name from t where city="苏州" order by name limit 10100
然后,再用归并排序的方法取得按 name 顺序第 10001~10100 的 name、id 的值,然后拿着这 100 个 id 到数据库中去查出所有记录。
posted @   eahy  阅读(112)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
· 【译】Visual Studio 中新的强大生产力特性
· 2025年我用 Compose 写了一个 Todo App
点击右上角即可分享
微信分享提示