一條反直覺的索引設計鐵律
查詢:`SELECT TOP 20 * FROM t_article WHERE datetime > DATEADD(day,-7,getdate()) ORDER BY countView DESC`
**建立索引時,必須把 `countView` (ORDER BY 欄位) 放在最前面,`datetime` (WHERE 範圍條件) 放在後面,索引才會被使用。**
驗證環境
- SQL Server 2005 測試通過。
- 此行為在 2008/2012/2014/2016/2019 依然成立(優化器規則未變)。
原理剖析
- 索引鍵順序決定 **B+ 樹排序順序**。
- `ORDER BY countView DESC` 需要索引已按 `countView` 排序,才能直接從葉節點順序掃描前 20 筆(避免 Sort 運算子)。
- `WHERE datetime > ...` 是 **範圍掃描**,若 `datetime` 在前,索引按時間排序,`countView` 亂序,**無法滿足 ORDER BY**,優化器只能選 Clustered Index Scan + Sort,或建議建立 `countView` 領先的索引。
- 把 `countView` 放前面,索引按熱門度排序,**Top 20 直接從索引頭部取**,再套用 `datetime` 篩選(Residual Predicate),極少量行即滿足,總成本極低。
正確索引定義
CREATE NONCLUSTERED INDEX IX_t_article_countView_datetime
ON t_article (countView DESC, datetime)
INCLUDE (其他 SELECT 欄位); -- 覆蓋查詢避免 Key Lookup
延伸:複合索引欄位順序通則
- **等值條件 (`=`) 在前** — 選擇性最高、可 Seek 定位。
- **排序欄位 (`ORDER BY`) 緊接在後** — 滿足排序避免 Sort。
- **範圍條件 (`>`, `<`, `BETWEEN`, `LIKE '%...'`) 在最後** — 只能做 Residual Predicate 或 Range Scan 終止。
- **INCLUDE 非鍵欄位** — 覆蓋查詢,不計入鍵長度限制 (900 bytes),不影響排序。
驗證腳本
-- 查看執行計畫是否出現 Index Seek + Ordered:True + Top
SET STATISTICS IO, TIME ON;
SELECT TOP 20 *
FROM t_article
WHERE datetime > DATEADD(day,-7,getdate())
ORDER BY countView DESC;
結語
**ORDER BY 欄位優先、範圍條件欄位靠後** — 這是「Top N 排序查詢」索引設計的黃金法則。違背它,就是送給 Sort 運算子 CPU 週期與 tempdb I/O 的禮物。