查詢重寫層面
- 拒絕子查詢入 WHERE:改用 JOIN / APPLY / CTE,避免重複執行
- 時間查詢不套函數:
WHERE create_date >= '2023-01-01' AND create_date < '2023-02-01',禁用 TO_CHAR/YEAR/MONTH
- 高選擇性條件後置:Oracle/舊版優化器自下而上解析 WHERE,最強過濾條件寫最後 (現代優化器多無關,但習慣保持)
- FROM 最後的表 = 驅動表:多表 JOIN 時,小表/過濾後小結果集放最後 (Oracle 舊版 RBO);現代 CBO 看統計資料,但寫法仍建議小表在前
- 三表以上 JOIN 必有交叉表:A-B-C 連接,B 為交叉表 (被 A 和 C 都引用),B 應為驅動表
- 綁定變數:
WHERE id = :b1 而非拼接字串,共用執行計畫、防注入
- WHERE 少用 OR:改
UNION ALL / IN / EXISTS
- EXISTS 替代 IN:
WHERE EXISTS (SELECT 1 FROM B WHERE B.id = A.id) 通常優於 IN (SELECT id FROM B)
- NOT EXISTS 替代 NOT IN:
NOT IN 遇 NULL 陷阱,NOT EXISTS 安全高效
- 索引欄不計算:
WHERE sal*12 > 25000 → WHERE sal > 25000/12
- IN 替代 OR:
WHERE loc_id IN (10,15,20)
- 索引欄避免 IS NULL/IS NOT NULL:B-tree 不存 NULL (Oracle),改用函數索引 / 位圖索引 / 預設值
- 組合索引必走前導欄:
INDEX (A,B,C) 查詢必含 A,否則掃描
- UNION ALL 替代 UNION:無去重需求省排序
- 避免隱含轉換:
WHERE empno = '123' (empno 為數值) → TO_CHAR(empno)='123' 索引失效,改 empno = 123
- != 導致掃描:改寫為範圍或 NOT EXISTS
- 優化 GROUP BY:只 SELECT 分組欄 + 聚合,盡早過濾 (WHERE > HAVING)
- LIKE 前導萬用字元不可用索引:
'%YE' 掃描,'YE%' Seek → 全文檢索/反轉索引
- 拒絕複雜正則:
LIKE '98___' → WHERE zipcode BETWEEN '98000' AND '98999' 走索引
- 明確 SELECT 欄位 + 表別名:禁用
SELECT *,減少 I/O、鎖、網路、解析開銷
索引設計鐵律
- 高選擇性欄位優先建立 B-tree 索引
- 組合索引順序:= 條件欄位在前,範圍條件在後
- 覆蓋索引:
INCLUDE 非鍵欄位,避免 Key Lookup
- 過濾索引:
WHERE status = 1 只索引活躍資料
- 定期
UPDATE STATISTICS / REBUILD INDEX 消除碎片
分頁查詢最佳實踐
-- SQL Server 2012+ 標準語法
SELECT col1, col2
FROM dbo.BigTable
ORDER BY id
OFFSET @PageSize * (@PageNum - 1) ROWS
FETCH NEXT @PageSize ROWS ONLY;
-- 大表深分頁優化:延遲關聯
SELECT t.col1, t.col2
FROM (
SELECT id FROM dbo.BigTable ORDER BY id
OFFSET @PageSize * (@PageNum - 1) ROWS FETCH NEXT @PageSize ROWS ONLY
) p
JOIN dbo.BigTable t ON t.id = p.id;
動態 SQL 組裝規範
- 參數化:
sp_executesql @SQL, N'@id INT', @id
- QUOTENAME 防注入:
QUOTENAME(@Schema) + '.' + QUOTENAME(@Table)
- 避免在迴圈中 EXEC,改用表值參數/臨時表批次處理