一句話導讀
ORDER BY
不能走索引時,MySQL 會在 Server 層做一次 filesort。內部實現分 單路(全字段) 與 雙路(rowid) 兩種;了解它們的觸發條件、判別方法與調優思路,是 SQL 性能優化的必修課。
一、為什么會有 filesort?
當查詢無法利用 覆蓋索引 或 索引順序 滿足
ORDER BY
時,MySQL 需要把結果集讀出來再排序。這個排序邏輯統稱 filesort,但它未必落盤,絕大多數情況下在內存完成。
二、單路 vs 雙路:一張圖看懂差異
階段 | 單路排序 (Single-Pass) | 雙路排序 (Two-Pass) |
---|---|---|
讀取列 | 所有查詢列一次性讀入 sort buffer | 只讀?排序鍵 + rowid |
排序對象 | 完整記錄 | <排序鍵, rowid> ?二元組 |
回表 | 不需要 | 按 rowid?二次回表取整行 |
內存消耗 | 高(存整行) | 低(只存鍵+id) |
I/O 特征 | 順序讀一次 | 隨機讀兩次 |
典型觸發 | 查詢列總字節 ≤?max_length_for_sort_data | 超過閾值或含大?TEXT/BLOB |
三、內部流程拆解
單路排序
掃表/索引 → 把需要的 所有列 拷進 sort_buffer
在內存(或磁盤臨時文件)里按排序鍵快排/歸并
直接返回結果給客戶端
雙路排序
只取 排序鍵 + 聚簇主鍵(rowid) 進 sort buffer
排序后得到“排好序的 rowid 列表”
按 rowid 順序回表 取其余列 → 返回
四、如何查看 MySQL 使用了哪一種?
MySQL 不直接寫“單路/雙路”字樣,而是把信息藏在 optimizer trace 與 EXPLAIN FORMAT=json 里。
方法 1:EXPLAIN FORMAT=json(MySQL 8.0 推薦)
EXPLAIN FORMAT=json
SELECT * FROM orders
WHERE order_date >= '2025-01-01'
ORDER BY total_amount DESC LIMIT 20\G
在輸出里查找:
"filesort_information": [{"sort_mode": "<sort_key, rowid>" <-- 雙路/* 或 "<sort_key, additional_fields>" */ <-- 單路}
]
<sort_key, rowid>
→ 雙路<sort_key, additional_fields>
或<sort_key, packed_additional_fields>
→ 單路
方法 2:optimizer trace(所有版本通用)
-- 會話級開啟
SET optimizer_trace="enabled=on";
-- 執行目標 SQL
SELECT ... ORDER BY ...;
-- 查看 trace
SELECT * FROM information_schema.optimizer_trace\G
搜索關鍵字:
"filesort_summary": {"sort_mode": "<sort_key, rowid>"
}
含義同上。
方法 3:慢查詢日志 / performance_schema(線上無侵入)
MySQL 8.0.13+ 的 慢日志 JSON 會記錄
"sort_mode"
字段。performance_schema 表
events_statements_history_long
中:SUM_SORT_ROWS
累計排序行數SUM_SORT_ROW_ID
> 0 可側面反映雙路排序
五、調優策略速查表
目標 | 手段 |
---|---|
避免 filesort | 建立覆蓋索引?(order_col, ...) ,使?EXPLAIN ?出現?Using index |
保持單路 | 減少查詢列寬度;避免?SELECT * ;調大?max_length_for_sort_data |
降低內存壓力 | 若列過大,可接受雙路;或把大?TEXT/BLOB ?拆子表延遲加載 |
加速排序 | 調大?sort_buffer_size(會話級);確保?tmp_table_size/max_heap_table_size?足夠 |
示例調優:
-- 會話級只對當前連接生效
SET sort_buffer_size = 4*1024*1024; -- 4 MB
SET max_length_for_sort_data = 4096; -- 允許更長列走單路
六、實戰案例
場景:訂單寬表 orders
30+ 列,含 TEXT
備注字段。
初始 SQL:
SELECT * FROM orders
WHERE order_date >= '2025-01-01'
ORDER BY total_amount DESC
LIMIT 20;
EXPLAIN FORMAT=json
看到 "sort_mode": "<sort_key, rowid>"
,慢日志顯示 Sort_row_id: 125000
。
→ 觸發雙路+大量回表,耗時 1.2 s。
優化:
去掉
*
只取需要的 5 列,列寬 < 3 KB新建復合索引
(order_date, total_amount DESC)
并 覆蓋查詢列
結果:
EXPLAIN
出現Using index; Using filesort
消失查詢降至 12 ms,CPU 降 90%。
七、結論
單路排序 用內存換 I/O,適合小字段;
雙路排序 用 I/O 換內存,適合大字段;
通過
EXPLAIN FORMAT=json
或optimizer_trace
查看sort_mode
即可判定;真正的高性能優化是 讓排序走索引,徹底告別 filesort。
一句話:看不到 Using filesort
,才是 ORDER BY
的終極答案。