在SQL SERVER的查詢語句中使用OR是否會致使不走索引查找(Index Seek)或索引失效(堆表走全表掃描 (Table Scan)、彙集索引表走彙集索引掃描(Clustered Index Scan))呢?是否全部狀況都是如此?又該如何優化呢? 下面咱們經過一些簡單的例子來分析理解這些現象。下面的實驗環境爲SQL SERVER 2008,若是在不一樣版本有所區別,歡迎指正。 sql
堆表單索引 數據庫
首先咱們構建咱們測試須要實驗環境,具體狀況以下所示:app
DROP TABLE TEST
CREATE TABLE TEST (OBJECT_ID INT, NAME VARCHAR(32));
CREATE INDEX PK_TEST ON TEST(OBJECT_ID)
DECLARE @Index INT =0;
WHILE @Index < 500000
BEGIN
INSERT INTO TEST
SELECT @Index, 'kerry'+CAST(@Index AS VARCHAR(6));
SET @Index = @Index +1;
END
UPDATE STATISTICS TEST WITH FULLSCAN
場景1:以下所示,並非全部的OR條件都會致使SQL走全表掃描。具體狀況具體分析,不要套用教條。性能
SELECT * FROM TEST WHERE (OBJECT_ID =5 OR OBJECT_ID = 105)
場景2:加了條件1=1後,執行計劃從索引查找(Index Seek)變爲全表掃描(Table Scan),爲何會如此呢?我的理解爲優化器將OR運算拆分爲兩個子集處理,因爲一些緣由,1=1這個條件致使優化器認定須要全表掃描才能完成1=1條件子集的計算處理(爲了理解這個,煞費苦心,鑑於理論薄弱,若有錯誤或不足,敬請指出)。因此優化器在權衡代價後生成的執行計劃最終選擇了全表掃描(Table Scan)測試
SELECT * FROM TEST WHERE (1=1 OR OBJECT_ID =105);
場景3: 下面場景比較好理解,由於下面須要從500000條記錄中取出499700條記錄,而全表掃描(Table Scan)確定是最優的選擇,代價(Cost)最低。優化
SELECT * FROM TEST WHERE (OBJECT_ID >300 OR OBJECT_ID =105);
場景4:這種場景跟場景2的狀況本質是同樣的。因此在此略過。其實相似這種寫法也是實際狀況中最常出現的狀況,還在迷糊的同窗,趕忙拋棄這種寫法吧spa
DECLARE @OBJECT_ID INT =150;
SELECT * FROM TEST WHERE (@OBJECT_ID IS NULL OR OBJECT_ID =@OBJECT_ID);
彙集索引表單索引 3d
在彙集索引表中,咱們也依葫蘆畫瓢,準備實驗測試的數據環境。code
DROP TABLE TEST
CREATE TABLE TEST (OBJECT_ID INT, NAME VARCHAR(32));
CREATE CLUSTERED INDEX PK_TEST ON TEST(OBJECT_ID)
DECLARE @Index INT =0;
WHILE @Index < 500000
BEGIN
INSERT INTO TEST
SELECT @Index, 'kerry'+CAST(@Index AS VARCHAR(6));
SET @Index = @Index +1;
END
UPDATE STATISTICS TEST WITH FULLSCAN
場景1 :索引查找(Index Seek) server
SELECT * FROM TEST WHERE (OBJECT_ID =5 OR OBJECT_ID = 105)
場景2:彙集索引掃描(Clustered Index Scan)
場景3:彷佛與堆表有所不一樣。彙集索引表竟然仍是走彙集索引查找。
場景4:OR致使彙集索引掃描
若是堆表或彙集索引表上創建有聯合索引,狀況也大體如此,在此不作過多案例講解。下面僅僅講述一兩個案例場景。
DROP TABLE test1;
CREATE TABLE test1
(
a INT,
b INT,
c INT,
d INT,
e INT
)
DECLARE @Index INT =0;
WHILE @Index < 10000
BEGIN
INSERT INTO test1
SELECT @Index,
@Index,
@Index,
@Index,
@Index
SET @Index = @Index + 1;
END
CREATE INDEX idx_test_n1
ON test1(a, b, c, d)
UPDATE STATISTICS test1 WITH fullscan;
SELECT * FROM TEST1 WHERE A=12 OR B> 500 OR C >100000
由於結果集是幾個條件的並集,最多隻能在查找A=12的數據時用索引,其它幾個條件都須要表掃描,那優化器就會選擇直接走一遍表掃描,以最低的代價COST完成,因此索引就失效了。
那麼如何優化查詢語句含有的OR的SQL語句呢?方法無外乎有三種:
1:經過索引覆蓋,使包含OR的SQL走索引查找(Index Seek)。可是這個只能知足部分場景,並不能解決全部這類SQL。這個Solution具備必定的侷限性。
SELECT * FROM TEST1 WHERE A=12 OR B=500
若是咱們經過索引覆蓋,在字段B上面也創建索引,那麼下面OR查詢也會走索引查找。
CREATE INDEX IDX_TEST1_B ON TEST1(B);
SELECT * FROM TEST1 WHERE A=12 OR B=500
2:使用IN替換OR。 可是這個Solution也有不少侷限性。在此不作過多闡述。
3:通常將OR的字句分解成多個查詢,而且經過UNION ALL 或UNION鏈接起來。在聯合索引或有索引覆蓋的場景下。大部分狀況下,UNION ALL的效率更高。可是並非全部的UNION ALL都會比OR的SQL的代價(COST),特殊的狀況或特殊的數據分佈也會出現UNION ALL比OR代價要高的狀況。例如,上面特殊的要求,從全表中取兩條記錄,以下所示
SELECT * FROM TEST1 WHERE A=12
UNION ALL
SELECT * FROM TEST1 WHERE B=500
UNON ALL語句的代價(Cost)要高與OR是由於它作了兩次索引查找(Index Seek),而OR語句只作一次索引查找(Index Seek)就完成了。開銷明顯小一些,可是實際狀況這類特殊狀況比較少,實際狀況的取數條件、數據都比這個簡單案例要複雜得多。因此在大部分狀況下,拆分爲UNION ALL語句的效率要高於OR語句
另一個案例,就是最上面實驗的堆表TEST, 在字段OBJECT_ID上建有索引
SELECT * FROM TEST WHERE (OBJECT_ID >300 OR OBJECT_ID =105);
SELECT * FROM TEST WHERE OBJECT_ID >300
UNION ALL
SELECT * FROM TEST WHERE OBJECT_ID =105;
能夠從下面看出二者開銷不一樣的地方在於IO方面,二者開銷之因此有區別,是由於第二個SQL多了一次掃描(索引查找)
總結:
在實際開發環境中,OR這種寫法確實會帶來不少不肯定性,儘可能使用UNION 或IN替換OR。咱們須要遵循一些規則,可是也不能認爲它就是一成不變的,永爲真理。具體場景、具體環境具體分析。要知其然知其因此然。在微軟亞太區數據庫技術支持組的官方博客中就有一個案例SQL Server性能問題案例解析 (3)也是OR引發的性能案例。 博客中有個觀點,我以爲挺讚的:」須要注意的是,對於OR或UNION,並無肯定的孰優孰劣,使用時要進行測試才能肯定。「 。