聚簇索引 vs 次級索引 (Clustered vs Secondary)

★★★★★Reviewed

FromDDIAHigh

📚 From the Books

聚簇索引 vs 次級索引 (Clustered vs Secondary)Indexing

索引存什麼,決定了它是「資料本身」還是「指路牌」。Clustered index 把整列資料放在葉節點(資料即索引);Secondary index 葉節點只放指標,要拿完整列得再跳一次(回表)。這一刀切下去,決定了寫入成本、回表代價,與「該不該把欄位塞進索引」。

🧠 Intuition

TIP

一句話分辨:clustered index 是「書的內文本身按章節排好」——找到位置就是內容;secondary index 是「書末的索引頁」——找到的是頁碼,還得翻回去那一頁。

⚖️ Tradeoffs

Clustered (IOT) — 主鍵查詢與範圍掃描極快
  • 以主鍵點查或範圍掃描時,找到葉節點就拿到整列,零回表
  • 主鍵相鄰的列物理相鄰,range scan 是順序 I/O。
  • 代價:每個 secondary index 都要存主鍵當指標,主鍵越肥、所有次級索引越肥。所以 InnoDB 主鍵宜窄、宜單調遞增(避免頁分裂)。
Heap table — 寫入便宜,但讀靠回表
  • 資料無序追加進 heap,插入不必維持物理順序,寫入路徑單純。
  • 所有索引一律是 secondary,葉節點存實體位置(RID/ctid),命中後直接定位、不必經主索引。
  • 代價:沒有「資料即索引」的免費午餐,任何非覆蓋查詢都要回 heap;PostgreSQL 還因 MVCC 更新搬移列而需 HOT / index-only scan 搭配 visibility map 來緩解。
回表 (Bookmark Lookup) — secondary index 的隱藏成本
  • 每筆命中 = 一次索引存取 + 一次回表,後者通常是隨機 I/O。
  • 命中列數一多,optimizer 可能判定「回表太貴」而放棄索引、改全表掃描(tipping point)。
  • 解法是讓索引「覆蓋」查詢所需欄位(見覆蓋索引),把回表整個消掉。
InnoDB 指標 = 主鍵 vs Heap 指標 = 實體位置
  • InnoDB secondary index 存主鍵值(邏輯指標):列搬家(頁分裂)不必更新 secondary index,但回表要再走一次主鍵 B-Tree(兩次 B-Tree 查找)。
  • Heap 系統存實體位置(物理指標):回表一步到位,但列實體搬移時需維護指標(PostgreSQL 用 MVCC + HOT 規避,SQL Server heap 用 forwarding pointer,會造成 fragmentation)。
  • 取捨本質:邏輯指標換「搬家免維護」,物理指標換「回表更快」。

🔑 Takeaways

✍️ My Notes

2026-07-01

  • Q: 既然 clustered index 主鍵查那麼快,為什麼不把所有重要欄位都當主鍵?
  • A: 因為主鍵在 InnoDB 裡會被每一個 secondary index 當作指標複製進去。主鍵越寬,所有次級索引的葉節點越胖,磁碟與快取佔用全面上升、寫入也更慢。這就是為什麼最佳實務是用窄的、單調遞增的代理鍵(如 BIGINT auto-increment)當主鍵,把語意上的唯一鍵改用 unique secondary index 表達——讓 clustered key 保持精瘦。

📖 Further Reading