動態條件查詢常寫成:
WHERE (@ProjectIds IS NULL OR ProjectId = @ProjectIds)
AND (@Scores IS NULL OR Score = @Scores)
直覺以為優化器能「短路」,實測完全失效,索引不被使用。
CREATE TABLE aaa (id INT IDENTITY, name VARCHAR(12), age INT);
CREATE INDEX idx_age ON aaa(age);
INSERT INTO aaa(name, age)
SELECT CAST(number AS VARCHAR), number FROM master..spt_values WHERE type='P' AND number < 10000;
DECLARE @i INT = 100;
-- 1. 變數 IS NULL 在前
SELECT * FROM aaa WHERE (@i IS NULL OR age = @i); -- ❌ Clustered Index Scan
-- 2. 變數 IS NULL 在後
SELECT * FROM aaa WHERE (age = @i OR @i IS NULL); -- ❌ Clustered Index Scan
-- 3. ISNULL 包裝
SELECT * FROM aaa WHERE age = ISNULL(@i, age); -- ❌ Clustered Index Scan
-- 4. 直接比較 (對照組)
SELECT * FROM aaa WHERE age = @i; -- ✅ Index Seek
結論:SQL Server (同 Oracle) 遇到 變數 IS NULL 邏輯,優化器無法參數化嗅探,退化為全表掃描。
DECLARE @i INT = 100;
DECLARE @sql NVARCHAR(MAX) = N'SELECT * FROM aaa';
IF @i IS NOT NULL
SET @sql += N' WHERE age = @i';
EXEC sp_executesql @sql, N'@i INT', @i;
優點:只有真正有值時才加 WHERE,計畫穩定走索引。
DECLARE @ProjectIds INT = 5, @Scores INT = NULL;
DECLARE @sql NVARCHAR(MAX) = N'SELECT * FROM Comment WITH (NOLOCK) WHERE 1=1';
DECLARE @params NVARCHAR(MAX) = N'';
IF @ProjectIds IS NOT NULL BEGIN
SET @sql += N' AND ProjectId = @ProjectIds';
SET @params += N'@ProjectIds INT,';
END
IF @Scores IS NOT NULL BEGIN
SET @sql += N' AND Score = @Scores';
SET @params += N'@Scores INT,';
END
-- 移除尾隨逗號
SET @params = LEFT(@params, LEN(@params) - 1);
EXEC sp_executesql @sql, @params, @ProjectIds, @Scores;
| 方案 | 優點 | 缺點 | 適用 |
|---|---|---|---|
| 動態 SQL | 計畫最優、靈活 | 需防注入、除錯稍難 | 條件多、表大、高頻 |
| IF/ELSE 多條 SQL | 簡單直觀 | 條件組合爆炸 (2^n) | 條件極少 (≤2) |
| OPTION (RECOMPILE) | 無需動態 SQL | 每次重編譯、CPU 開銷 | 中小表、低並發 |
| 強制索引 HINT | 快速止血 | 硬編碼、維護難 | 緊急臨時 |