兩個把 secondary index 榨到極致的手法。Covering index:查詢需要的欄位全在索引裡,直接讀索引就回答、免回表。Composite index:多欄組成一把有序的複合鍵,但只能從最左前綴用起——欄位順序就是它的命。
🧠 Intuition
TIP
把 composite index 想成電話簿按「姓, 名」排序:你能用「姓」或「姓+名」快速找人,但**不能單用「名」**找——因為簿子不是按名排的。covering index 則是「連電話號碼都印在索引頁上」,查到就不必翻回內文。
- Covering index:索引包含查詢 SELECT/WHERE/ORDER BY 用到的所有欄位,optimizer 走 index-only scan,整個回表(bookmark lookup)被消掉。
- SQL Server 用
INCLUDE把非鍵欄位掛在葉節點(不參與排序、不算入鍵寬度);MySQL/PostgreSQL 則靠把欄位放進複合鍵、或 PG 的INCLUDE(covering index)達成。 - Composite index
(a, b, c):邏輯上依 a 排序,a 相同再依 b,依此類推。 - 最左前綴原則 (leftmost prefix):能高效服務
(a)、(a,b)、(a,b,c)的條件;無法用於(b)、(c)、(b,c)——因為沒有前導欄位,索引的有序性派不上用場。
⚖️ Tradeoffs
Covering index — 免回表,但讓索引變胖
- 收益:index-only scan 把「索引存取 + 回表隨機 I/O」砍成「只讀索引」,對高頻查詢是數量級的提升。
- 代價:塞進去的欄位讓索引葉節點變大、佔更多快取、寫入時要多維護一份。
- 折衷:SQL Server
INCLUDE/ PGINCLUDE把欄位放葉節點但不進鍵——既覆蓋查詢又不撐大 B-Tree 內部節點、不影響鍵排序。
欄位順序決定可用性 — composite 的成敗關鍵
(a, b)能服務WHERE a=?、WHERE a=? AND b=?、以及ORDER BY a, b。- 但
WHERE b=?用不到它——等於白建。 - 經驗法則:等值條件(equality)欄位放前面,範圍條件(range)放後面。一旦遇到 range 欄位,其後欄位就無法再用索引排序(range 之後失序)。
選擇性與排序 — 兩種排欄位的思路會打架
- 「高選擇性欄位放前面」能更快縮小掃描範圍(傳統建議)。
- 但若查詢有固定的 equality 前綴 + 之後 ORDER BY,欄位順序得遷就查詢模式而非單純選擇性,才能同時吃到 WHERE 過濾與免排序。
- 結論:composite index 是為具體查詢量身打造的,不存在「通用最佳順序」。
索引選擇 vs 寫入成本 — 別把每個查詢都餵一條索引
- 每多一條 secondary index,INSERT/UPDATE/DELETE 都要多維護一棵 B-Tree(寫放大、頁分裂、WAL 變多)。
- 過度索引會拖垮寫入、膨脹儲存,optimizer 也要在更多候選間挑選。
- 策略:用少數設計良好的 composite/covering index 覆蓋多種查詢(靠最左前綴一索引多用),勝過為每個查詢各建一條窄索引。
🔑 Takeaways
- Covering index 讓查詢所需欄位全在索引內 → index-only scan、消除回表,是最划算的索引優化之一。
- SQL Server / PG 的
INCLUDE能覆蓋查詢又不撐大鍵寬度,優於硬把欄位塞進複合鍵。 - Composite index 只能從最左前綴用起;欄位順序決定它能服務哪些查詢——equality 在前、range 在後。
- 索引不是越多越好:每條 secondary index 都是寫入稅,目標是用少數設計良好的索引覆蓋最多查詢。
2026-07-01
- Q: 我有
(a, b)索引,查詢是WHERE a = 1 AND b > 10 ORDER BY c,這索引幫得上多少? - A:
a = 1吃到 equality 前綴,b > 10是 range、能用索引繼續過濾——到這裡很好。但ORDER BY c吃不到:因為 b 是 range 條件,其後的順序在索引中已被打散,c 又不在索引內,所以 optimizer 仍得做一次 sort。若 c 排序很關鍵,得重新設計成(a, c)之類、犧牲 b 的索引過濾換取免排序——這正是「欄位順序服務具體查詢」的典型抉擇。