我们常常会在sql中使用order by关键字来对查询结果进行排序处理。
sort_buffer_size
参数控制,默认为 256 kb。
sort_buffer_size
)时,mysql 将会在内存中使用快速排序算法进行排序。sort_buffer_size
)时,mysql 将会借助临时磁盘文件使用归并排序算法进行排序(通常会将待排序数据分成多个“小文件”,对各个“小文件”进行快速排序,再汇总成一个有序的“大文件”)。概念:将最终结果集中所有的字段都放进 sort buffer中,然后在sort buffer中针对排序字段进行快速排序。
以下面的 SQL 为例子:
SELECT nick_name, age, phone FROM t_user WHERE city = "深圳" ORDER BY nick_name;
假设 city 字段上有索引,全字段排序的过程:
从 city 索引树上找到第一条值为深圳的数据,取得 id 之后回表取得 nick_name、age、phone 三个字段放入 sort buffer。
从 city 索引树取下一条值为深圳的数据,重复 1 过程,直到下一条数据不满足值为深圳条件。
到这一步,所有 city = 深圳 的数据都在 sort buffer 了,然后对 nick_name 进行快速排序。
将排序结果返回
概念:只将与排序相关的字段和 rowId 放入 sort buffer,其余结果集需要用到的数据在排序完成后,通过 rowId 回表取得。
还是之前那个例子:
SELECT nick_name, age, phone FROM t_user WHERE city = "深圳" ORDER BY nick_name;
rowId 排序全过程:
从 city 索引树上找到第一条值为深圳的数据,取得 id 之后回表取得 nick_name 这个与排序相关的字段和主键 id 一起放入 sort buffer。
从 city 索引树取下一条值为深圳的数据,重复 1 过程,直到下一条数据不满足值为深圳条件。
这时候,所有 city = 深圳 的数据都在 sort buffer 了(sort buffer 里面的数据包含两个字段: id 和 nick_name),然后对 nick_name 执行快速排序。
利用排序好的数据,使用主键 id 再次回表取其他字段,将结果返回。
注意:在步骤 4 中不会等所有排序好的 id 回表完再返回,而是每个 id 回表一次,取得该行数据之后立即返回,所以不会消耗额外的内存。
max_length_for_sort_data
SET max_length_for_sort_data=8; # 小于要排序字段长度的和即可。
如果单行数据的长度不大于该值,则使用全字段排序,否则使用rowId排序。
无论是使用全字段排序还是 rowId 排序,都不可避免了对所有符合 WHRER
条件的数据进行了排序。
优先队列进行排序的流程:
在所有待排序的数据,取数量为 LIMIT
(本例中为 3)的数据,构建一个堆
不断的取下一行数据,更新堆节点
当所有行的扫描完,得到最终的排序结果
EXPLAIN
进行执行过程分析的时候除了能看到 Using filesort
以外,还能看到 Using temporary
,代表在排序过程中使用到了临时表。tmp_table_size
参数限制了内存临时表的大小,默认值是 16M,如果临时表大小超过了tmp_table_size
,那么内存临时表就会转成磁盘临时表。internal_tmp_disk_storage_engine
参数控制,默认为 InnoDB
。max_length_for_sort_data
决定采用全字段排序还是 rowId 排序。tmp_table_size
限制时,则需要将内存临时表转换为磁盘临时表,这时候由于回表意味着随机读,所以会搭配全字段排序方式。