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

在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)

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的狀況本質是同樣的。因此在此略過。其實相似這種寫法也是實際狀況中最常出現的狀況,還在迷糊的同窗,趕忙拋棄這種寫法吧spa

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

clipboard[2]

 

彙集索引表單索引 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)

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,並無肯定的孰優孰劣,使用時要進行測試才能肯定。「 。

相關文章
相關標籤/搜索