SQL SERVER中關於OR會導致索引掃描或全表掃描的淺析


在SQL SERVER的查詢語句中使用OR是否會導致不走索引查找(Index Seek)或索引失效(堆表走全表掃描 (Table Scan)、聚集索引表走聚集索引掃描(Clustered Index Scan))呢?是否所有情況都是如此?又該如何優化呢? 下面我們通過一些簡單的例子來分析理解這些現象。下面的實驗環境為SQL SERVER 2008,如果在不同版本有所區別,歡迎指正。

 

堆表單索引

首先我們構建我們測試需要實驗環境,具體情況如下所示:

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)

clipboard

 

場景2:加了條件1=1后,執行計划從索引查找(Index Seek)變為全表掃描(Table Scan),為什么會如此呢?個人理解為優化器將OR運算拆分為兩個子集處理,由於一些原因,1=1這個條件導致優化器認定需要全表掃描才能完成1=1條件子集的計算處理(為了理解這個,煞費苦心,鑒於理論薄弱,如有錯誤或不足,敬請指出)。所以優化器在權衡代價后生成的執行計划最終選擇了全表掃描(Table Scan)

SELECT * FROM TEST WHERE (1=1 OR OBJECT_ID =105);

clipboard[1]

 

場景3: 下面場景比較好理解,因為下面需要從500000條記錄中取出499700條記錄,而全表掃描(Table Scan)肯定是最優的選擇,代價(Cost)最低。

SELECT * FROM TEST WHERE (OBJECT_ID >300 OR OBJECT_ID =105); 

 

場景4:這種場景跟場景2的情況本質是一樣的。所以在此略過。其實類似這種寫法也是實際情況中最常出現的情況,還在迷糊的同學,趕緊拋棄這種寫法吧

DECLARE @OBJECT_ID INT =150;
 
SELECT * FROM TEST WHERE (@OBJECT_ID IS NULL OR OBJECT_ID =@OBJECT_ID);

clipboard[2]

 

聚集索引表單索引

在聚集索引表中,我們也依葫蘆畫瓢,准備實驗測試的數據環境。

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)

 

SELECT * FROM TEST WHERE (OBJECT_ID =5 OR OBJECT_ID = 105)

 

場景2:聚集索引掃描(Clustered Index Scan)

clipboard[3]

 

場景3:似乎與堆表有所不同。聚集索引表居然還是走聚集索引查找。

clipboard[4]

 

場景4:OR導致聚集索引掃描

clipboard[5]

 

如果堆表或聚集索引表上建立有聯合索引,情況也大致如此,在此不做過多案例講解。下面僅僅講述一兩個案例場景。

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

clipboard[6]

 

因為結果集是幾個條件的並集,最多只能在查找A=12的數據時用索引,其它幾個條件都需要表掃描,那優化器就會選擇直接走一遍表掃描,以最低的代價COST完成,所以索引就失效了。

 

那么如何優化查詢語句含有的OR的SQL語句呢?方法無外乎有三種:

1:通過索引覆蓋,使包含OR的SQL走索引查找(Index Seek)。但是這個只能滿足部分場景,並不能解決所有這類SQL。這個Solution具有一定的局限性。

SELECT * FROM TEST1 WHERE A=12 OR B=500

clipboard[7]

如果我們通過索引覆蓋,在字段B上面也建立索引,那么下面OR查詢也會走索引查找。

CREATE INDEX IDX_TEST1_B ON TEST1(B);
 
SELECT * FROM TEST1 WHERE A=12 OR B=500 

clipboard[8]

 

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 

clipboard[9]

 

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;

clipboard[10]

可以從下面看出兩者開銷不同的地方在於IO方面,兩者開銷之所以有區別,是因為第二個SQL多了一次掃描(索引查找)

clipboard[11]

clipboard[12]

 

總結:

    在實際開發環境中,OR這種寫法確實會帶來很多不確定性,盡量使用UNION 或IN替換OR。我們需要遵循一些規則,但是也不能認為它就是一成不變的,永為真理。具體場景、具體環境具體分析。要知其然知其所以然。在微軟亞太區數據庫技術支持組的官方博客中就有一個案例SQL Server性能問題案例解析 (3)也是OR引起的性能案例。 博客中有個觀點,我覺得挺贊的:”需要注意的是,對於OR或UNION,並沒有確定的孰優孰劣,使用時要進行測試才能確定。“ 。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM