為什麼「排序」有時比「不排序」更快?
隨機磁碟 I/O (Random I/O) 是資料庫最大效能殺手。當查詢需訪問大量離散頁面時,主動排序將隨機讀取轉為順序讀取,反而大幅降低磁頭移動/旋轉延遲。本文解析此反直覺原理與適用場景。
核心原理:順序 vs 隨機 I/O 成本差
- 順序讀取:磁頭定位一次,連續讀取大量數據,吞吐接近磁碟頂限 (HDD ~150MB/s, SSD ~500MB/s+)
- 隨機讀取:每次定位需 **尋道時間 (4~10ms) + 旋轉延遲 (4ms)**,HDD 實際 IOPS 仅 100~150,SSD 雖好但仍有佇列延遲
- 成本比:隨機讀取單位成本約 **100~1000 倍** 於順序讀取
何時排序能勝過隨機讀取?
當查詢需讀取**大量不連續頁面**且**結果集需排序輸出**時,優化器若選擇「先按主鍵/索引排序,再順序讀取」,總成本可能低於「按原查詢順序隨機讀取再排序」。
典型場景
- 大表 JOIN 後排序:驅動表經過篩選後,被驅動表需大量隨機回表。若先將驅動表結果按被驅動表關聯鍵排序,回表變順序讀取。
- 分頁查詢深翻頁:`LIMIT 100000, 20` 需掃描 10 萬行。若有覆蓋索引按排序欄位掃描,則為順序讀取;無索引則是隨機回表。
- 批次更新/刪除:`WHERE id IN (大量離散 ID)` → 先 `ORDER BY id` 再執行,減少鎖競爭與隨機 I/O。
優化器行為與干預
- MySQL 8.0+ 引入 **Cost Model** 更精準評估排序成本
- 可用 `EXPLAIN ANALYZE` 觀察 `rows_examined` vs `rows_sorted`
- 強制排序:`SELECT * FROM t WHERE ... ORDER BY pk` (若 pk 為聚簇索引)
- 禁止排序:`ORDER BY NULL` (僅需任意順序時避免 filesort)
實測案例
某 5000 萬行訂單表,按 `user_id` 查詢近期 100 條訂單:
- 無索引 + `ORDER BY create_time DESC`:全表掃描 + filesort,**12.4 秒**
- 建立 `(user_id, create_time DESC)` 組合索引:**0.003 秒** (索引下推 + 避免排序 + 順序讀取)
- 僅建 `user_id` 索引 + `ORDER BY create_time`:回表隨機讀取 + filesort,**1.8 秒**
結論
**索引設計是根本**。合理的組合索引能同時滿足「篩選、排序、覆蓋」,將隨機 I/O 消滅在萌芽狀態。主動排序僅為無索引可用時的補救手段。