在日常的数据库使用中,一定会经常碰到需要根据指定的字段排序来显示结果的需求,创建一个市民表,假设你要查询城市是“杭州”的所有人名字,并且按照姓名排序返回前 1000 个人的姓名、年龄。
表结构
CREATE TABLE `t` (
`id` int(11) NOT NULL,
`city` varchar(16) NOT NULL,
`name` varchar(16) NOT NULL,
`age` int(11) NOT NULL,
`addr` varchar(128) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `city` (`city`)
) ENGINE=InnoDB;
查询SQL如下
select city,name,age from t where city='杭州' order by name limit 1000 ;

这条SQL虽然简单,但弄清楚它的排序过程,还是很有必要的。
MySQL中的排序主要就是在内存中进行的,这块内存的大小受到参数sort_buffer_size控制,同时受需要筛选与排序行的限制,又分为学两种排序方式:
1、全字段排序,max_length_for_sort_dat足够放下city,name,age三个字段1000行数据
2、根据rowid排序,max_length_for_sort_dat不够放下city,name,age三个字段1000行数据
max_length_for_sort_data,是 MySQL 中专门控制用于排序的行数据的长度的一个参数。它的意思是,如果单行的长度超过这个值,MySQL 就认为单行太大,要换一个算法。
下图分别对应分字段排序与根据rowid排序的实现过程:

无论上述两种,使用哪种,都是在sort_buffer中的排序,参数sort_buffer_size控制
1、sort_buffer足够放1000行数据(1、city,name,age)(2、id,name),此时不需要使用磁盘临时文件
2、sort_buffer不够时,需要使用到磁盘临时文件
mysql开启trace追踪SQL
为了确认该SQL使用了磁盘文件,开启trace追踪SQL
/* 打开optimizer_trace,只对本线程有效 */
SET optimizer_trace='enabled=on';
/* @a保存Innodb_rows_read的初始值 */
select VARIABLE_VALUE into @a from performance_schema.session_status where variable_name = 'Innodb_rows_read';
/* 执行语句 */
select city, name,age from t where city='杭州' order by name limit 1000;
/* 查看 OPTIMIZER_TRACE 输出 */
SELECT * FROM `information_schema`.`OPTIMIZER_TRACE`\G
/* @b保存Innodb_rows_read的当前值 */
select VARIABLE_VALUE into @b from performance_schema.session_status where variable_name = 'Innodb_rows_read';
/* 计算Innodb_rows_read差值 */
select @b-@a
结果图:

number_of_tmp_files 表示的是,排序过程中使用的临时文件数。
为什么需要 12 个文件?
内存放不下时,就需要使用外部排序,外部排序一般使用归并排序算法。
可以这么简单理解,MySQL 将需要排序的数据分成 12 份,每一份单独排序后存在这些临时文件中。然后把这 12 个有序文件再合并成一个有序的大文件。
如果 sort_buffer_size 超过了需要排序的数据量的大小,number_of_tmp_files 就是 0,表示排序可以直接在内存中完成。
否则就需要放在临时文件中排序。sort_buffer_size 越小,需要分成的份数越多,number_of_tmp_files 的值就越大。
优化方法
创建联合索引是较为合适的优化方法:
(当然SQL是否经常被使用到等问题,也是需要具体问题具体分析的)
使用索引覆盖,不需要回表,各字段在索引中都是排序过的
优化后的读取数据过程如下图:





