執行計畫與索引使用 (Execution Plans)

★★★★Reviewed

FromSQL Server 2025 Query Performance TuningHigh

📚 From the Books

執行計畫與索引使用 (Execution Plans)Query Processing

執行計畫是 optimizer 給你的「我打算怎麼拿這份資料」的自白書。看懂它,你才知道索引到底有沒有被用、慢在哪一步——而不是盲目加索引碰運氣。

🧠 Intuition

TIP

讀執行計畫先看兩件事:哪一步碰最多列(最寬的那條),以及估計列數 vs 實際列數差多少。前者是熱點,後者告訴你 optimizer 是不是被統計騙了。

⚖️ Tradeoffs

Seq Scan vs Index Scan vs Index-Only Scan
  • Seq scan:順序讀整張表。撈大比例的列或小表時最快(順序 I/O > 大量隨機回表)。
  • Index scan:走 B-Tree 找到符合的列,再回表取其他欄位。選擇度高(只撈少數列)時最划算。
  • Index-only scan / covering index:查詢需要的欄位全在索引裡,免回表。PG 還需 visibility map 夠新才能省掉 heap 存取。
  • tipping point:命中列數一多,optimizer 會判定回表太貴而從 index scan 切回 seq scan——這通常是對的。
Nested Loop vs Hash Join vs Merge Join
  • Nested loop:外表每一列去內表查一次。外表小 + 內表 join 欄有索引時最優;外表一大就是災難(O(n×m))。
  • Hash join:先把小表建 hash table,再掃大表探查。大表等值 join、無有用索引時的主力;需記憶體,不夠就 spill 到磁碟。
  • Merge join:兩邊各自排序後像拉鍊般合併。雙方已排序(或有可用索引序)時最優,否則排序成本可能不划算。
  • 選錯的根因幾乎都是 cardinality 估計:以為內表小選 nested loop,實際很大就慢到爆。
為何索引沒被用 (Index Not Used)
  • 型別轉換 (implicit conversion)WHERE varchar_col = 123,欄位被隱式轉型,索引失效;或 WHERE id = '5' 兩邊型別不符。
  • 函式包住欄位 (non-sargable)WHERE YEAR(created_at) = 2026WHERE UPPER(name) = 'X'WHERE col + 1 > 10——欄位被運算包住,B-Tree 無法定位,得改寫成範圍條件或建 function-based / computed index。
  • 低選擇度 (low selectivity):條件命中表的大部分列(如 status='active' 佔 90%),optimizer 算出回表比全表掃描還貴,故意不用索引——這是正確決定。
  • 前綴不符:複合索引 (a, b) 卻只查 b,用不到(leftmost prefix rule)。
N+1 問題 — ORM 的隱形殺手
  • 抓 N 筆主資料(1 條查詢),再為每一筆各發一條查詢抓關聯(N 條),總共 N+1 條往返。
  • 單看每條查詢計畫都漂亮(走索引、毫秒級),但網路往返與固定開銷 × N 會把整體拖垮——計畫工具看不出來,要看查詢「條數」。
  • 解法:JOIN fetch / JOIN FETCH(JPA)、batch fetching(IN (...) 一次撈)、DataLoader 模式。不是索引問題,是查詢數量問題。

🔑 Takeaways

✍️ My Notes

2026-07-01

  • Q: 我查詢明明有建索引,EXPLAIN 卻顯示 Seq Scan,是不是索引壞了?
  • A: 大概率沒壞,是 optimizer 判斷不用比較划算,常見三類原因。一是低選擇度:你的條件命中了表裡一大半的列,逐筆走索引回表的隨機 I/O 比直接順序掃整表還貴,這時 seq scan 是正解。二是寫法讓索引失效(non-sargable):例如 WHERE YEAR(created_at)=2026 或欄位型別被隱式轉換,B-Tree 無法用來定位,得把運算從欄位上拿掉(改成 created_at >= '2026-01-01' AND < '2027-01-01')。三是統計過期讓 optimizer 估錯選擇度。先用 EXPLAIN ANALYZE 對照估計列數與實際列數:差很多就是統計或寫法問題;數字接近、就是它真的算出來該掃整表。先確認是哪一類,再決定要改寫、更新統計、還是接受它。

📖 Further Reading