SQL 是聲明式語言,寫法微小差異可能導致執行計劃天壤之別。統一規範能降低認知負擔、減少線上事故、提升團隊協作效率。以下 11 條規範提煉自大規模生產環境血淚教訓。
HOLDLOCK/SERIALIZABLE 隔離級別,除非業務極度要求幻讀保護。-- 反例:三次掃描 orders 大表
SELECT * FROM orders WHERE cust_id IN (SELECT cust_id FROM vip);
SELECT * FROM orders WHERE status = 1;
SELECT * FROM orders WHERE amount > 1000;
-- 正例:一次掃描物化
SELECT * INTO #vip_orders FROM orders WHERE cust_id IN (SELECT cust_id FROM vip);
-- 後續複用 #vip_orders
FAST_FORWARD(只讀、只進)、避免游標循環內再做表連接。WHERE 高選擇性等值列 = ? AND 範圍列 > ? AND 低選擇性列 = ?。| 反例(索引失效) | 正例(SARGable) |
|---|---|
WHERE YEAR(create_time) = 2024 | WHERE create_time >= '2024-01-01' AND create_time < '2025-01-01' |
WHERE DATEDIFF(day, dt, GETDATE()) < 7 | WHERE dt >= DATEADD(day, -7, GETDATE()) |
WHERE ISNULL(name, '') = 'test' | WHERE name = 'test' OR name IS NULL |
WHERE amount * 1.1 > 100 | WHERE amount > 100 / 1.1 |
-- 慢:全表掃描計數
IF (SELECT COUNT(*) FROM big_table WHERE id = @id) > 0
-- 快:短路掃描
IF EXISTS (SELECT 1 FROM big_table WHERE id = @id)
COUNT(1) 比 COUNT(*) 略快(避免展開元數據),但 EXISTS 才是本質優化。
整數列:WHERE id > 100 → WHERE id >= 101。優化器可直接定位到 101 的索引頁,而 > 需先定位 100 再前移。差異微小但習慣養成無成本。
OR 往往導致索引掃描 → 改寫為 UNION ALL(去重用 UNION)分別走索引再合併。IN (subquery) 在舊版本可能退化為相關子查詢 → 改寫為 EXISTS 或 JOIN。IN (值列表) 值超過幾百個 → 考慮臨時表 + JOIN,避免編譯超時/計劃膨脹。VARCHAR(20) JOIN VARCHAR(50) → 隱式轉換、索引失效、校對集衝突報錯。-- 反例:參數長度大於列,隱式截斷不報錯
CREATE PROC usp_get @name VARCHAR(100) AS SELECT * FROM t WHERE col = @name; -- col 是 VARCHAR(20)
-- 正例:參數長度 = 列長度,或用 TYPE
CREATE PROC usp_get @name VARCHAR(20) AS ... -- 或
CREATE PROC usp_get @name AS t.col%TYPE ... -- Oracle 風格,SQL Server 需手動對齊
ALTER TABLE SET (LOCK_ESCALATION = AUTO/DISABLE) 控制)。DECLARE @batch_size INT = 5000, @rows INT = 1;
WHILE @rows > 0
BEGIN
DELETE TOP (@batch_size) FROM big_table WHERE create_time < '2023-01-01';
SET @rows = @@ROWCOUNT;
WAITFOR DELAY '00:00:00.1'; -- 釋放鎖、讓出 CPU
END
SQLFluff / TSQLLint 靜態掃描。規範不是束縛,而是團隊共識的「護欄」。好規範讓正確的寫法成為最省力的寫法,讓錯誤無處遁形。