| 算法 | MySQL 版本 | 流程 | 特點 |
| 雙路排序 (Two-pass) | ≤ 4.0 / 兼容模式 | 1. 讀取排序鍵 + 行指針 (rowid) 2. 在 sort_buffer 排序 3. 按排序後 rowid 回表讀取其餘列 | 兩次訪問表,**I/O 開銷大**,佔用 sort_buffer 少 |
| 單路排序 (Single-pass) | ≥ 4.1 (預設) | 1. 一次性讀取 **所有 SELECT 列** 2. 在 sort_buffer 排序 3. 直接輸出結果 | **一次訪問表,I/O 少**,但佔用 sort_buffer **大幅增加** (含所有列數據) |
算法選擇判斷邏輯
MySQL 比較 所有 SELECT 列的總長度 (含變長列實際長度) 與參數 max_length_for_sort_data (默認 1024 字節):
- 總長度 <
max_length_for_sort_data → **單路排序**
- 總長度 ≥
max_length_for_sort_data → **雙路排序**
關鍵點:max_length_for_sort_data 比較的是 **列定義最大長度** 還是 **實際數據長度**?實測為 **實際數據長度** (VAR 類型按實際存儲長度),但 BLOB/TEXT 等大字段強制雙路。
三大優化手段
1. 增大 max_length_for_sort_data — 強制單路排序
[mysqld]
max_length_for_sort_data = 8192 # 8KB,視業務列總長調整
- 適用場景:內存充足、SELECT 列總長略超默認 1024、希望減少雙路 I/O。
- 風險:單路排序佔用更多 sort_buffer,若
sort_buffer_size 不夠 → **分片排序 + 磁盤臨時表** → 性能暴跌。
2. 精簡 SELECT 列 — 讓總長度自然滿足單路條件
- 嚴禁
SELECT * — 尤其含 TEXT/BLOB/JSON 大字段時,直接強制雙路甚至無法排序 (報錯 The used SELECT statements have a different number of columns 等)。
- 只查業務必需列,將大字段移至應用層二次查詢或異步加載。
- 配合覆蓋索引 (
Using index) 可完全避免 Filesort。
3. 調大 sort_buffer_size — 減少分片排序與磁盤臨時表
[mysqld]
sort_buffer_size = 2M # 默認 256K,建議 2-8M,連接數極高時謹慎調大
- 連接級參數**:每個連接首次排序時 **一次性分配** 設定大小內存。
- 不是越大越好**:
- 高併發 (如 1000 連接) × 8M = 8GB 內存壓力。
- 超過 2M 可能觸發
mmap() 而非 malloc(),分配開銷增大 (內核版本相關)。
- 監控指標:
Sort_merge_passes (合併排序次數) > 0 說明分片發生,需調大 buffer 或精簡列。
進階優化模式
模式 A:覆蓋索引 + 索引排序 (終極形態)
-- 查詢
SELECT id, name, create_time FROM orders WHERE user_id=100 ORDER BY create_time DESC;
-- 索引
KEY idx_uid_ct (user_id, create_time DESC, id, name); -- 覆蓋查詢列 + 排序列
-- EXPLAIN: type=ref, Using index
零 Filesort、零回表、零臨時表。
模式 B:Limit 小偏移 + 索引排序 (分頁神器)
SELECT id FROM huge_table ORDER BY create_time DESC LIMIT 10 OFFSET 1000000;
-- 若有索引 (create_time DESC),MySQL 只需掃描索引前 1000010 行,無 Filesort。
-- 深分頁 (OFFSET 大) 仍需掃描大量索引行,建議改用「書籤/游標分頁」: WHERE create_time < 'last_seen_time' ORDER BY ... LIMIT 10
模式 C:強制索引 / 忽略索引 (慎用)
SELECT * FROM t FORCE INDEX (idx_a) WHERE b=1 ORDER BY a; -- 強制使用 idx_a 排序
SELECT * FROM t IGNORE INDEX (idx_b) WHERE b=1 ORDER BY a; -- 忽略誤導優化器的索引
避坑清單
ORDER BY RAND() → **全表掃描 + 臨時表 + Filesort**,百萬級表直接拖垮實例。替代:應用層隨機 ID、WHERE id >= (SELECT FLOOR(MAX(id)*RAND())) ORDER BY id LIMIT 1 (需 ID 連續)。
ORDER BY 非索引列 + LIMIT 大偏移 → Filesort 全量排序再丟棄大部分。改用游標分頁或延遲關聯。
JOIN + ORDER BY 非驅動表列 → 無法使用索引排序。嘗試調整驅動表或冗余排序列到驅動表。
UNION ALL ... ORDER BY → 臨時表合併後排序。子查詢各自排序再合併 (MySQL 8.0+ 支援 WITH RECURSIVE 優化)。
監控與診斷 SQL
-- 會話級排序統計
SHOW SESSION STATUS LIKE 'Sort_%';
/*
Sort_merge_passes -- 合併次數 >0 表示分片落盤
Sort_range -- 範圍排序次數
Sort_rows -- 排序行數
Sort_scan -- 全表掃描排序次數
*/
-- 慢查詢中 Filesort 特徵
# Time: 2023-01-01T10:00:00
# Query_time: 5.2 Lock_time: 0.0 Rows_sent: 10 Rows_examined: 1000000
# Sort_merge_passes: 12 -- 關鍵指標
SET timestamp=...;
SELECT * FROM t WHERE c=1 ORDER BY d LIMIT 10;
總結:**優先讓優化器選擇 Using Index**;次之 **精簡列 + 適度調大 max_length_for_sort_data 強制單路**;最後 **調大 sort_buffer_size 兜底分片**。**嚴禁 SELECT *、嚴禁 ORDER BY RAND()、嚴禁大偏移分頁**。建立「慢查詢必看 Sort_merge_passes」研發規範,從源頭遏制排序災難。
- 上一篇文章: MySQL slave_net_timeout 參數調優:解決低寫入場景下從庫頻繁重連告警
- 下一篇文章: MySQL 5.6 ICP (Index Condition Pushdown) 索引下推:原理、實測與使用限制全解析
Copyright ©
程式碼之家 All Rights Reserved