文件排序(filesort)的优化与避免
什么是 filesort
当 MySQL 无法使用索引完成排序时,就需要自己进行排序操作,这个操作称为 filesort(文件排序)。
EXPLAIN SELECT * FROM order WHERE status = 1 ORDER BY create_time DESC;
-- Extra: Using where; Using filesort ← 存在 filesort
注意:filesort 不一定是”文件”排序——如果排序数据量较小,在内存中完成;数据量大时才会使用磁盘文件。但无论如何,它都比索引排序慢。
为什么 filesort 比索引排序慢
索引排序
当 ORDER BY 字段是索引的”最左前缀”时,MySQL 可以直接按索引顺序读取数据:
索引排序的过程:
1. 扫描索引 B+Tree
2. 按顺序从叶子节点取出主键
3. 回表获取完整行数据
4. 直接返回结果(有序)
→ 不需要额外的排序操作,没有额外的 CPU/内存/IO
Filesort 排序
当 ORDER BY 字段不能使用索引时:
Filesort 的过程:
1. 扫描表/索引,获取排序字段值 + 主键/行指针
2. 在 sort buffer 中对这些值进行排序
3. 如果 sort buffer 放不下,使用磁盘临时文件做归并排序
4. 按排序后的顺序回表获取完整行
→ 额外的 CPU 消耗(排序)+ 可能的 IO 消耗(磁盘临时文件)
哪些情况会产生 filesort
-- 1. ORDER BY 字段不是索引列
SELECT * FROM user ORDER BY register_time;
-- 2. ORDER BY 和 GROUP BY 字段不同
SELECT category, COUNT(*) FROM product GROUP BY category ORDER BY COUNT(*);
-- 3. 跨表排序
SELECT u.* FROM user u JOIN order o ON u.id = o.user_id
ORDER BY o.amount;
-- 4. ORDER BY 字段不满足最左前缀
-- 索引:(a, b, c)
ORDER BY b; -- 未从最左列开始
ORDER BY a, c; -- 跳过了 b
-- 5. 混合排序方向
-- 索引:idx(a ASC, b ASC)
ORDER BY a ASC, b DESC; -- 方向不一致
如何查看 filesort
-- 方式 1:EXPLAIN
EXPLAIN SELECT * FROM user ORDER BY register_time LIMIT 10;
-- Extra: Using filesort
-- 方式 2:状态变量
FLUSH STATUS;
SELECT * FROM user ORDER BY register_time LIMIT 10;
SHOW STATUS LIKE 'Sort%';
-- Sort_merge_passes: 归并排序的轮次(> 0 表示使用了磁盘临时文件)
-- Sort_range: 范围扫描排序次数
-- Sort_rows: 总排序行数
-- Sort_scan: 全表扫描排序次数
优化策略
策略 1:建立合适的索引(最有效)
这是最根本的解决方案。通过建立包含排序字段的联合索引,让排序直接走索引。
-- 原始查询
SELECT * FROM order WHERE status = 1 ORDER BY create_time DESC LIMIT 10;
-- 建立联合索引
CREATE INDEX idx_status_create ON order(status, create_time);
-- 优化后
SELECT * FROM order WHERE status = 1 ORDER BY create_time DESC LIMIT 10;
-- Extra: Using where (不再有 Using filesort)
-- type: ref(走索引)
关键:索引列的顺序必须与 WHERE + ORDER BY 的条件匹配。
策略 2:使用覆盖索引
-- 需要排序字段包含在索引中
CREATE INDEX idx_status_create_amount ON order(status, create_time, amount);
-- 只查索引包含的字段
SELECT status, create_time, amount
FROM order
WHERE status = 1
ORDER BY create_time DESC;
-- Extra: Using where; Using index(完全使用索引覆盖,效率最高)
策略 3:减少排序数据量
-- 先过滤再排序,减少参与排序的行数
SELECT * FROM order
WHERE status = 1 AND create_time > '2024-01-01' -- 先过滤
ORDER BY create_time DESC
LIMIT 10;
-- 不加过滤条件会导致排序大量行
策略 4:增大 sort buffer
# my.cnf
# 增大排序缓冲,减少磁盘排序
sort_buffer_size = 2M # 默认 256K,通常 1M-8M
sort buffer 是每个连接独立分配的,设置过大可能导致内存占用过高。
策略 5:优化排序方向
-- 统一排序方向可以走索引
-- 索引:idx_create(create_time ASC)
ORDER BY create_time ASC; -- 走索引
ORDER BY create_time DESC; -- 走索引(倒序扫描)
-- 以下情况无法走索引
ORDER BY create_time ASC, id DESC; -- 混合方向
Filesort 的两种模式
MySQL 在 filesort 时有两种模式,可以通过 max_length_for_sort_data 控制:
模式一:单行排序(Sort Tuple)
-- 当行数据很小时
-- sort buffer 中只存排序字段和主键
-- 排序完成后,按主键回表获取完整数据
模式二:全行排序(Sort Row)
-- 当行数据很大时
-- sort buffer 中存放排序字段和完整行数据
-- 不需要回表,但占用更多 sort buffer 空间
# 控制两种模式的切换
max_length_for_sort_data = 4096 # 默认 4096 字节
# 超过此值使用 Sort Row 模式
# 适当增大可以减少回表次数
监控和诊断
-- 检查系统的 filesort 情况
SHOW GLOBAL STATUS LIKE 'Sort%';
-- 理想的系统状态
Sort_merge_passes = 0 -- 没有磁盘排序
Sort_rows << table_rows -- 不会对全表排序
排序优化的优先级:
索引排序 > 单行 filesort(内存)> 多行 filesort(内存)> 磁盘 filesort
面试要点
- filesort 的本质是无法利用索引排序时的”兜底方案”
- Extra: Using filesort 就是需要优化的信号
- 最有效的优化是创建合适的索引(排序字段包含在索引中)
- 不是所有 filesort 都需要优化——数据量小(< 1000 行)时内存排序可以接受
- 区分”内存 filesort”和”磁盘 filesort”,后者才是真正的性能杀手
- 能通过 SHOW STATUS LIKE ‘Sort%’ 中的 Sort_merge_passes 判断是否产生了磁盘排序
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END


暂无评论内容