一、索引與存取路徑
- 避免在索引欄位上運算:`WHERE Salary * 12 > 50000` → 導致 Index Scan。改寫 `WHERE Salary > 50000/12` 或建立計算欄位+索引。
- 複合索引首欄必須出現在 WHERE/JOIN/ORDER BY:索引 `(A,B,C)`,查詢 `WHERE B=1` 無法用到索引。
- 高選擇性欄位優先:複合索引順序應為「最常用且最具過濾力」的欄位在前。
- 覆蓋索引:`INCLUDE` 非鍵欄位,讓查詢不回表(Key Lookup)。
- 避免 `IS NULL` / `IS NOT NULL` 破壞 SARGability:改用預設值+索引或篩選索引。
- `!=` / `<>` 通常導致掃描:若需排除少數值,考慮 `UNION ALL` 正向篩選。
- 隱式轉換殺手:`WHERE VarcharCol = 123` → 隱式 `CAST(VarcharCol AS INT)`。統一參數型別、欄位型別。
- LIKE 尾碼萬用字元可用索引:`LIKE 'ABC%'` → Seek;`LIKE '%ABC'` → Scan。前綴搜尋考慮全文索引或反向欄位。
二、查詢結構與運算子
- 用 `EXISTS` 替代 `IN`(子查詢):`EXISTS` 遇真即停,`IN` 需物化子查詢結果集。`NOT EXISTS` 遠優於 `NOT IN`(後者對 NULL 語意複雜且難優化)。
- 避免 `OR` 破壞索引使用:`WHERE A=1 OR B=2` → 多半 Scan。拆分為 `UNION ALL` 兩條求集合。
- 慎用 `DISTINCT` / `ORDER BY` / `GROUP BY`:需排序/雜湊,耗 TempDB。確認業務真需要,或在索引順序滿足時省略。
- 子查詢改寫為 JOIN 或 APPLY:相關子查詢每列執行一次,`OUTER APPLY`/`CROSS APPLY` 可集合化。
- 合併 `UNION` → `UNION ALL`:除非業務需去重,`UNION ALL` 免排序/雜湊,快數倍。
- 避免 `SELECT *`:增加網路傳輸、阻礙覆蓋索引、Schema 變更風險。明確列出欄位。
三、聯結策略
- 驅動表選小不選大:Nested Loop 外層宜小(少列)、內層有索引。Hash Join 適合大表聯大表且無索引。
- 提供充足的 JOIN 條件:缺少條件導致 Cartesian Product 或錯誤的 Join Order。每張表至少一個等值條件連接。
- 資料型別一致:`INT` JOIN `VARCHAR` → 隱式轉換 + Scan。統一型別或顯式 CAST。
四、進階技巧
- 綁定變數 / 參數化查詢:重用執行計畫、防 SQL Injection、減少編譯 CPU。
- 優化 `GROUP BY` / 彙總:預先聚合、使用索引順序避免 Sort、考慮 Columnstore 索引(OLAP)。
- 定期維護統計資料與索引:`UPDATE STATISTICS`、`ALTER INDEX REBUILD/REORGANIZE`,避免計畫偏離實際資料分布。
快速自檢清單
| 症狀 | 可能原因 | 對策 |
| 執行計畫出現 Table Scan / Index Scan | 無索引、SARGability 破壞、統計過期 | 建立索引、改寫 WHERE、UPDATE STATISTICS |
| Key Lookup 佔成本 90%+ | 非覆蓋索引 | 加 INCLUDE 欄位或調整查詢欄位 |
| TempDB 壓力大、排序溢寫 | 大量 DISTINCT/ORDER BY/GROUP BY/HASH JOIN | 索引排序、分頁查詢、增加記憶體 |
| 參數化查詢偶爾極慢 | Parameter Sniffing | OPTION (RECOMPILE)、OPTIMIZE FOR、Plan Guide |
效能調優無銀彈,唯有「測量 → 找瓶頸 → 改寫/加索引 → 再測量」的循環。