前言:
filesort 出现在当无法使用索引排序时,MySQL 必须自己计算排序顺序,这个过程称为 filesort。EXPLAIN 的 Extra 字段会出现 Using filesort。常见触发场景:
-
排序列不在索引中,或顺序/方向与索引不一致
-
ORDER BY包含RAND()等函数 -
跨表排序(如
JOIN后对非驱动表的列排序) -
查询范围过大(如
WHERE条件不能有效缩小索引前缀匹配)
一、filesort 的内部原理
1.filesort 的核心步骤
-
根据
WHERE条件筛选数据行。 -
为每一行构造一个排序键(sort key,包括排序列以及查询需要的其他列)。
-
对排序键进行排序。
-
按排序后的顺序返回最终结果。
依据内存和配置的不同,filesort 有两种算法:
2.双路排序(Two-pass / Old algorithm)
-
过程:
-
只将排序列和行的主键(或行指针)放入排序缓冲区(
sort_buffer_size)。 -
排序完成后,再根据主键回表读取需要的全部列。
-
-
适用 :当排序列 + 主键的总大小较大,或待排数据量较大且
max_length_for_sort_data较小时。 -
特点:对内存需求小,但需要两次访问表(排序后回表),产生更多随机 I/O。
3.单路排序(Single-pass / New algorithm)
-
过程:
-
将排序列和查询所需的所有列都放入排序缓冲区。
-
排序完成后直接得到全部数据,无需回表。
-
-
适用 :当排序缓冲区足够大,且每行的总长度小于
max_length_for_sort_data时。 -
特点:避免回表,但内存压力大;若缓冲区放不下,会分割成多个块并写入临时文件,再进行归并。
4.切换控制变量
-
sort_buffer_size:排序缓冲区大小(线程级别)。 -
max_length_for_sort_data:当单行总长度超过此阈值时,MySQL 会采用双路排序。默认值通常为 1024 字节。
单路排序并不绝对优于双路排序。如果 sort_buffer_size 太小,单路排序会频繁生成磁盘临时文件,性能反而不如双路。
2.filesort 的完整执行流程
-
读取数据 :根据
WHERE条件利用索引或全表扫描,收集待排序行。 -
构建排序键:
-
单路模式:键 = 排序列 + SELECT 中所有列(含表达式等)。
-
双路模式:键 = 排序列 + 行 ID(如 InnoDB 的主键或 ROWID)。
-
-
内存排序 :在
sort_buffer_size内尝试使用快速排序(quicksort)排序。 -
磁盘归并 :若内存放不下,将排序块写入临时文件(
Using temporary files),然后对多个文件进行归并排序。 -
返回结果:
-
双路模式:按排序后的行 ID 依次回表读取剩余列。
-
单路模式:直接输出缓冲区中的数据。
-
二、如何避免 filesort
-
建立正确的索引
-
让
ORDER BY的列成为某个索引的一部分,且顺序、方向一致。 -
注意
WHERE条件中的等值查询可以充当索引前缀。 -
避免
ORDER BY不同方向(如ASC,DESC)混用,除非 MySQL 8.0 支持降序索引。
-
-
优化查询本身
-
只取必要的列(减小单行长度,促使使用单路排序或覆盖索引)。
-
当
ORDER BY与非覆盖索引的WHERE条件冲突时,可尝试重写查询。
-
-
调整系统参数
-
增加
sort_buffer_size:但需注意这是线程级内存,过高会耗尽全局资源。 -
适当提高
max_length_for_sort_data:让更多场景使用单路排序(但要观察内存压力)。 -
设置
max_sort_file_size限制磁盘临时文件大小。
-
-
使用覆盖索引
如果索引包含了所有查询需要的列,
ORDER BY可直接走索引排序并返回,没有filesort,也没有回表。
三、filesort 快慢与优化建议
-
filesort不一定慢 :如果排序数据能完全在sort_buffer_size内存中完成,速度很快(通常比索引排序略慢,但差异不大)。 -
慢的情况 :当排序数据量超出
sort_buffer_size,需要生成磁盘临时文件并做归并排序时,会伴随大量磁盘 I/O,此时filesort会显著变慢。 -
是否需要优化 :视查询性能而定。如果
EXPLAIN显示Using filesort但查询响应时间可以接受,不必过度优化 ;只有当你观察到排序成为瓶颈(慢查询日志、高Sort_merge_passes状态变量)时,才需要针对性优化。
1.如何判断 filesort 是否慢?
执行查询后查看状态变量:
SHOW SESSION STATUS LIKE 'Sort%';
-
Sort_merge_passes> 0 → 表示使用了磁盘临时文件归并,性能较差。 -
Sort_range/Sort_rows等可看排序行数。
如果 Sort_merge_passes 长期为 0,说明 filesort 完全在内存完成,速度足够快,不需要优化
2.单路 vs 双路:需要优化吗?
不需要直接"优化"算法本身 (因为 MySQL 会自动选择合适的算法),但可以通过调整系统参数来影响算法的选择,进而提升排序性能。
| 参数 | 作用 | 优化建议 |
|---|---|---|
sort_buffer_size |
排序缓冲区大小(线程级) | 适当调大(如 2M~16M),可以减少磁盘归并,让更多排序在内存完成。过高会浪费内存,甚至导致 OOM。 |
max_length_for_sort_data |
单路排序的行总长度阈值 | 如果查询需要很多列且内存充足,可适当提高(如 4096 字节),让 MySQL 优先使用单路排序,避免回表。 |
3.单路与双路的选择逻辑:
-
当每行排序键 + 所需列的总长度 ≤
max_length_for_sort_data→ 使用单路(一次排序出所有列,不回表)。 -
否则 → 使用双路(只排序列 + 行指针,排序后回表)。
优化原则:
-
若单行数据很长(如 TEXT/BLOB 列),双路反而更优(内存压力小)。
-
若回表代价高(随机 I/O 严重),且内存充足,可以提高阈值强制使用单路。
-
一般保持默认值即可,只有当 profiling 显示大量回表或磁盘排序时才调整。
四、常见误区与检查方法
-
误区 :"filesort 一定会用磁盘文件"
事实:只要数据能放入
sort_buffer_size,filesort 完全在内存完成,EXPLAIN仍然显示Using filesort。 -
检查方法 :通过
EXPLAIN查看Extra列;开启优化器追踪(optimizer_trace)可看到是否使用索引排序;观察SHOW STATUS中的Sort_merge_passes(非零表示使用了磁盘归并)。
总结:优化优先顺序
-
首选:用索引避免 filesort
这是性能最佳的方案,彻底消除排序开销。
-
次选:接受内存 filesort
如果无法索引排序,但
Sort_merge_passes=0,说明全部在内存完成,性能尚可,不优化。 -
最后:调参缓解磁盘 filesort
如果
Sort_merge_passes > 0,按顺序尝试:-
增加
sort_buffer_size(减少归并次数) -
调整
max_length_for_sort_data(让单路/双路更适合你的场景) -
减小结果集(
WHERE更精确,LIMIT提前截断)
-
以上均为个人观点!
以上均为个人观点!
以上均为个人观点!