MySQL实战第八篇

102 阅读4分钟

order by 是怎么工作的?

select city,name,age from t where city='杭州' order by name limit 1000 ;

对于这个排序语句来说,我们通常会给city加上索引,然后我们通过explain命令来查看该语句的执行情况。

img

其中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 的值不满足查询条件为止;

6.对 sort_buffer 中的数据按照字段 name 做快速排序;

7.按照排序结果取前 1000 行返回给客户端。

我们把这个排序过程,成为全字段排序。

img

按照name排序这个动作,可能在内存中完成,也有可能使用外部排序,这取决于所遇内存和参数sort_buffer_size。

sort_buffer_size,就是 MySQL 为排序开辟的内存(sort_buffer)的大小。如果要排序的数据量小于 sort_buffer_size,排序就在内存中完成。但如果排序数据量太大,内存放不下,则不得不利用磁盘临时文件辅助排序。

我们可以通过查看OPTIMIZER_TRACE来查看是否使用了临时文件: SELECT * FROM infornation_schema.OPTIMIZER_TRACE\G

执行后我们可以看到

number_of_tmp_files表示是否使用了临时文件和使用的个数

examined_rows表示参与排序的行数

sort_mode表示排序的字段

当查询的字段有很多时,sort_buffer里要放的字段太多,内存里放下的行数很少,要分成很多个临时文件,排序的性能会变差。这时我们可以让MySQL采用另一种算法,我们先设置:SET max_length_for_sort_data = 16;

max_length_for_sort_data表示如果单行的长度超过了这个值,就要换另一种算法。

这时我们再去执行排序语句,由于长度小了,放入sort_buffer的只有排序的列(即name字段)和主键id

这时执行流程如下:

1.初始化 sort_buffer,确定放入两个字段,即 name 和 id;

2.从索引 city 找到第一个满足 city='杭州’条件的主键 id,也就是图中的 ID_X;

3.到主键 id 索引取出整行,取 name、id 这两个字段,存入 sort_buffer 中;

4.从索引 city 取下一个记录的主键 id;重复步骤 3、4 直到不满足 city='杭州’条件为止,也就是图中的 ID_Y;

5.对 sort_buffer 中的数据按照字段 name 进行排序;

6.遍历排序结果,取前 1000 行,并按照 id 的值回到原表中取出 city、name 和 age 三个字段返回给客户端。

这个执行流程的示意图如下,我把它称为 rowid 排序。

img

相比于全字段排序,rowId排序多了一次回表的流程,但是由于sort_buffer变小了,所需要的临时文件也变小少了。

这两种排序方式得出结论:

如果 MySQL 实在是担心排序内存太小,会影响排序效率,才会采用 rowid 排序算法,这样排序过程中一次可以排序更多行,但是需要再回到原表去取数据。

如果 MySQL 认为内存足够大,会优先选择全字段排序,把需要的字段都放到 sort_buffer 中,这样排序后就会直接从内存里面返回查询结果了,不用再回到原表去取数据。

这也就体现了 MySQL 的一个设计思想:如果内存够,就要多利用内存,尽量减少磁盘访问。

是不是所有order by 都需要排序呢?

其实并不是,我们上面的操作之所以会产生临时表,是因为要用来做排序操作,如果我们查询出来的数据本来就是有序的,那就不需要再排序了。

我们可以给要判断的条件的字段和order by的字段设置联合索引

alter table t add index city_user(city, name);

这样查出来的数据本来就是有序的了,这样就只需在做一次回表得到结果集就可以了。

甚至我们可以创建一个 city、name 和 age 的联合索引

alter table t add index city_user_age(city, name, age);

这样都不需要回表,直接取出这三个字段的值作为结果集直接返回。