許多開發人員擔心 WHERE 條件順序影響效能,實則 SQL Server 擁有強大的「查詢分析優化器」,能自動計算搜尋條件並選擇最佳索引。但理解其運作原理(特別是 SARG 概念)對編寫高效 SQL 至關重要。
優化器會分析 WHERE 子句每個階段,判斷是否可作為「掃描參數」(Search ARGument,SARG)。符合 SARG 形式的條件可利用索引快速縮小掃描範圍;不符合則需全表掃描逐行判斷。
SARG 形式:
欄位名 運算子 <常數 或 變數>
或
<常數 或 變數> 運算子 欄位名
欄位名與常數/變數分居運算子兩側。範例:
Name = '張三'價格 > 50005000 < 價格Name = '張三' AND 價格 > 5000name LIKE '張%' → 屬 SARG,可用索引name LIKE '%張' → 非 SARG,前置 % 導致索引失效,全表掃描Name = '張三' AND 價格 > 5000 → 符合 SARGName = '張三' OR 價格 > 5000 → 非 SARG,引發全表掃描典型非 SARG 形式:
NOT、!=、<>、!<、!>、NOT EXISTS、NOT IN、NOT LIKEABS(價格) < 5000、Name LIKE '%三'特例:WHERE 價格 * 2 > 5000 優化器可轉換為 WHERE 價格 > 2500 視為 SARG,但不建議依賴此行為,因無法保證完全等價。
SELECT * FROM table1 WHERE tid IN (2,3)
-- 等同於
SELECT * FROM table1 WHERE tid = 2 OR tid = 3
即使 tid 有索引也會失效。
許多資料稱 EXISTS 優於 IN,但實測(使用 pubs 資料庫,開啟 STATISTICS IO):
-- 使用 IN
SELECT title, price FROM titles
WHERE title_id IN (SELECT title_id FROM sales WHERE qty > 30)
-- 掃描計數 18,邏輯讀 56
-- 使用 EXISTS
SELECT title, price FROM titles
WHERE EXISTS (SELECT * FROM sales WHERE sales.title_id = titles.title_id AND qty > 30)
-- 掃描計數 18,邏輯讀 56
兩者 I/O 統計完全一致,效能無差異。NOT EXISTS 與 NOT IN 同理。
-- CHARINDEX 版本
SELECT gid, title, fariqi, reader FROM tgongwen
WHERE CHARINDEX('刑偵支隊', reader) > 0 AND fariqi > '2004-5-5'
-- 7秒,掃描計數 4,邏輯讀 7155
-- LIKE 前置% 版本
SELECT gid, title, fariqi, reader FROM tgongwen
WHERE reader LIKE '%' + '刑偵支隊' + '%' AND fariqi > '2004-5-5'
-- 7秒,掃描計數 4,邏輯讀 7155
無效能優勢,CHARINDEX 甚至較難閱讀。
一般建議用 UNION 替代 OR,大部分情況有效:
-- OR 版本:68秒,邏輯讀 404008
SELECT * FROM Tgongwen WHERE fariqi = '2004-9-16' OR gid > 9990000
-- UNION 版本:9秒,邏輯讀 67489
SELECT * FROM Tgongwen WHERE fariqi = '2004-9-16'
UNION
SELECT * FROM Tgongwen WHERE gid > 9990000
但當 OR 兩側查詢欄位相同時,UNION 反而較慢:
-- OR 版本:6423毫秒,掃描計數 2
SELECT * FROM Tgongwen WHERE fariqi = '2004-9-16' OR fariqi = '2004-2-5'
-- UNION 版本:11640毫秒,掃描計數 8
SELECT * FROM Tgongwen WHERE fariqi = '2004-9-16'
UNION
SELECT * FROM Tgongwen WHERE fariqi = '2004-2-5'
原因:UNION 需合併去重,增加額外開銷。
SELECT TOP 10000 gid, fariqi, reader, title FROM tgongwen ORDER BY gid DESC -- 4673ms
SELECT TOP 10000 gid, fariqi, title FROM tgongwen ORDER BY gid DESC -- 1376ms
SELECT TOP 10000 gid, fariqi FROM tgongwen ORDER BY gid DESC -- 80ms
每減少一個欄位,速度顯著提升,幅度取決於欄位大小。
SELECT COUNT(*) FROM Tgongwen -- 1500ms
SELECT COUNT(gid) FROM Tgongwen -- 1483ms (主鍵)
SELECT COUNT(fariqi) FROM Tgongwen -- 3140ms
SELECT COUNT(title) FROM Tgongwen -- 52050ms
COUNT(*) 與 COUNT(主鍵) 速度相當,且比其他欄位快。推測引擎自動選擇最小欄位統計。明確寫 COUNT(主鍵) 更直接。
-- 無 ORDER BY(fariqi 為聚集索引):196ms
SELECT TOP 10000 gid, fariqi, reader, title FROM tgongwen
-- ORDER BY gid(主鍵非聚集):4720ms
SELECT TOP 10000 gid, fariqi, reader, title FROM tgongwen ORDER BY gid ASC
-- ORDER BY gid DESC:4736ms
SELECT TOP 10000 gid, fariqi, reader, title FROM tgongwen ORDER BY gid DESC
聚集索引欄位排序快 3/10。小數據量差異明顯,大數據量(10萬+)差異縮小。
測試 100 萬筆資料:2004/1/1 後 50 萬筆(僅 2 個不同日期,精確到日);之前 50 萬筆(5000 個不同日期,精確到秒)。
SELECT * FROM Tgongwen WHERE fariqi > '2004-1-1' ORDER BY fariqi -- 6390ms
SELECT * FROM Tgongwen WHERE fariqi < '2004-1-1' ORDER BY fariqi -- 6453ms
結論:日期欄位包含時分秒不會降低查詢速度。