在MySQL的查询执行过程中,filesort是一个经常被误解的概念。它并不是指数据库一定把数据写入了磁盘上的某个排序文件,而是表示MySQL无法直接使用索引本身的有序性来完成ORDER BY,因此需要在拿到数据之后,额外进行一次排序操作。这个排序可能在内存里完成,也可能需要借助临时文件。当我们在EXPLAIN语句的输出中看到Extra列出现Using filesort时,就说明本次查询触发了这种额外的排序阶段。

一、filesort的底层原理
MySQL的优化器在处理带有ORDER BY的语句时,会优先判断排序字段是否和索引顺序一致。如果一致,存储引擎按索引扫描出来的数据天然有序,就不需要额外排序。反之,如果ORDER BY的列不在可用索引的排序路径上,或者查询中还混合了其他导致索引失效的条件,优化器就会选择filesort。
filesort的实现并不简单粗暴地“写文件”,它有两种主要策略。一种是早期的单行排序,即把查询需要返回的整行数据(包括排序字段和其他列)都放进排序缓冲,根据排序字段排好序后再返回。另一种是打包排序,只把排序字段和主键(或行指针)放进缓冲,排完序再回表取其他列。后者能节省内存,但可能增加回表次数。具体采用哪种,由max_length_for_sort_data等参数影响。
1.1 内存与临时文件
排序使用的内存区域由sort_buffer_size控制。如果待排序的数据量小于该缓冲,MySQL会在内存中完成快速排序。一旦超过,MySQL会把部分数据写入由tmpdir指定的临时文件,使用归并排序算法分阶段处理,这时才真正涉及磁盘IO,性能下降会比较明显。
我们可以通过调整sort_buffer_size让更多排序留在内存,但要注意它是每个排序线程独占的,设置过大在高并发时会导致内存吃紧。同时,tmpdir所在磁盘的性能也会直接影响落盘排序的速度。
二、触发filesort的常见场景
最常见的触发情况是ORDER BY的列没有索引,或者索引列顺序与ORDER BY不匹配。例如表上有索引(a,b),但查询写成ORDER BY b,就无法利用该索引的有序性。另外,当查询使用范围条件(如WHERE a > 10)后再ORDER BY b,由于a是范围,b在索引里不再连续有序,也常导致filesort。
还有一种隐蔽场景是使用了DISTINCT、GROUP BY或某些函数导致中间结果集需要先物化,再排序。以及当SELECT里包含了过大的字段(如longtext),使得单行排序成本过高,优化器可能改用打包排序但仍标记filesort。理解这些场景有助于我们在写SQL时有意识地规避。
2.1 示例说明
下面这段SQL在没有合适索引时就会出现filesort:
EXPLAIN SELECT id, name, age FROM user WHERE age > 20 ORDER BY name;
假设user表只在age上有索引,ORDER BY name无法利用该索引,Extra会显示Using where; Using filesort。我们需要建立(age, name)的联合索引,才能让范围过滤后依然按name有序。
三、如何优化filesort
最直接的优化手段是建立与ORDER BY顺序一致的联合索引,并尽量让WHERE条件落在索引前缀上。例如对上述查询建立INDEX(age, name),MySQL可以先用age > 20过滤,再沿着索引顺序取出name有序的数据,从而避免额外排序。如果业务允许,也可以只排序索引覆盖的列,减少回表。
除此之外,减少SELECT返回的列宽、控制排序数据量也很关键。深分页场景如LIMIT 100000, 20配合ORDER BY很容易触发大缓冲排序,可改为基于上一页最大ID的游标分页。适当增大sort_buffer_size、使用更快的临时目录、避免SELECT *,都能缓解filesort带来的开销。
3.1 利用索引覆盖避免排序
如果查询只涉及索引列,可写成如下形式,让索引既过滤又排序:
-- 建立索引 INDEX(age, name) EXPLAIN SELECT name FROM user WHERE age > 20 ORDER BY name;
此时Extra可能只显示Using where; Using index,完全没有filesort,因为数据从索引本身有序读出,无需重排。这也是高性能排序查询的设计方向。
四、如何确认filesort是否真的落盘
仅仅看到Using filesort不能断定发生了磁盘IO。我们可以开启optimizer_trace,观察filesort_summary里的memory_available、rows以及外部排序相关字段。如果number_of_tmp_files为0,说明全程在内存;大于0才表示用了临时文件。
在生产环境,结合慢查询日志的Rows_examined与Rows_sent比例,也能侧面判断排序代价。若两者差距巨大且查询慢,多半是filesort加大量临时数据导致。通过针对性加索引和改写SQL,通常能把这类查询的耗时降低一个数量级。
4.1 简单追踪示例
可用如下方式查看优化器对排序的抉择:
SET optimizer_trace = 'enabled=on'; SELECT id, name FROM user WHERE age > 20 ORDER BY name; SELECT * FROM information_schema.optimizer_traceG SET optimizer_trace = 'enabled=off';
在输出的trace里搜索filesort关键字,能看见使用的算法与缓冲情况,从而确认是否真的发生了外部排序,而不是仅凭EXPLAIN妄下结论。
五、总结
filesort本质上是MySQL在缺少索引有序性支持时进行的额外排序动作,不一定写盘,但确实是查询性能的常见杀手。我们通过合理设计联合索引、缩小结果集、控制列宽以及利用游标分页,可以大幅减少它的负面影响。理解其内部的内存与临时文件机制,才能在做参数调优和SQL改写时有的放矢,而不是盲目增大缓冲或随意加索引。