ORDER BY 是 SQL 查詢中常見的排序操作。MySQL 有兩種排序實現方式:利用有序索引直接回傳(Using index)與 檔案排序(Using filesort)。前者效能極佳,後者在大資料量時會消耗大量 CPU/記憶體甚至落盤。本文深入解析兩者的觸發條件、優化技巧與實戰案例。
| 模式 | EXPLAIN 顯示 | 原理 | 效能特徵 |
|---|---|---|---|
| 索引排序 | Using index | 直接按索引順序掃描,無需額外排序 | O(N) 線性掃描,極快,不佔用 sort_buffer |
| Filesort | Using filesort | 在 sort_buffer 中排序,再按 rowid 回表 | O(N log N),大資料會用臨時檔案,佔用 CPU/IO |
CREATE TABLE `test` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`rdate` datetime NOT NULL,
`inventid` int(11) NOT NULL,
`customerid` int(11) NOT NULL,
`staffid` int(11) NOT NULL,
`data` varchar(20) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_rdate` (`rdate`,`inventid`,`customerid`),
KEY `idx_inventid` (`inventid`),
KEY `idx_customerid` (`customerid`),
KEY `idx_staffid` (`staffid`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
EXPLAIN SELECT inventid FROM test
WHERE rdate='2011-12-14 00:00:00'
ORDER BY inventid, customerid;
分析:WHERE rdate=常數 固定了聯索引 uk_rdate 的第一欄,ORDER BY inventid, customerid 完全符合索引後續順序,且 SELECT inventid 為索引欄位 → Using index。
EXPLAIN SELECT * FROM test
WHERE rdate='2011-12-14 00:00:00'
ORDER BY staffid;
分析:staffid 不在 uk_rdate 中,MySQL 必須先按 uk_rdate 篩選,再對結果集 Filesort。
EXPLAIN SELECT * FROM test
WHERE rdate='2011-12-14 00:00:00'
ORDER BY inventid, customerid;
分析:SELECT * 包含非索引欄位 data,需回表,破壞索引順序優勢 → Using filesort。
| 演算法 | 觸發條件 | 記憶體使用 | 備註 |
|---|---|---|---|
| 雙路排序 | 排序欄位總長度 > max_length_for_sort_data (預設 1024 bytes) | 只存 sort_key + rowid,排序後再回表讀完整行 | MySQL 4.1+ 預設,雙倍 IO |
| 單路排序 | 排序欄位總長度 ≤ 閾值 | 一次讀取所有 SELECT 欄位至 sort_buffer 排序 | 記憶體佔用大,但單次 IO,MySQL 8.0 預設 |
SELECT *,縮減單路排序記憶體佔用。(WHERE欄位, ORDER BY欄位, SELECT欄位) 實現 Using index。ORDER BY ... LIMIT 10 觸發「優先佇列排序」,只保留 Top-N,大幅降低排序成本。ORDER BY a ASC, b DESC 無法使用索引(MySQL 8.0 除外)。CREATE INDEX idx_desc ON t_order (create_time DESC, id DESC);
-- 現在可支援 ORDER BY create_time DESC, id DESC 使用索引
Using index 是排序效能的聖杯,關鍵在於「WHERE 固定索引前綴 + ORDER BY 遵循索引順序 + 覆蓋索引」。若無法達成,善用 LIMIT、覆蓋索引、調整 sort_buffer_size 可大幅緩解 Filesort 壓力。EXPLAIN 時務必關注 Extra 欄位,將 Filesort 視為優化切入點。