SQL Server索引進階第七篇:過濾的索引

在之前的文章中,我們已經介紹了很多有關索引的知識,不管是對聚集索引還是非聚集索引,有一點我們可以知道就是:在底層數據表中的每一行,在索引頁中都有一個條目與之前對應,換句話說,就是,如果底層的數據有,假設10w條,那麼在索引結構中必然會包含10w個引用指向這些數據行。


SQL Server 2008以後的版本中,引入了一個特殊的“過濾的索引”改變了 這種情況,而且也相應的帶來了很多的好處,例如節省磁盤空間,節省內存,節省CPU,最後使得性能提升。


那麼,我們本篇文章就來看看如何使用這個“過濾的索引“。



過濾的索引的使用



首先,我們來看看過濾的索引的定義方法,其實很簡單,“過濾“顧名思義就是加上條件判斷,過濾的索引,就是在定義索引的使用,加上過濾條件,示例代碼如下:

  1. IF EXISTS ( SELECT * FROM sys.indexes
  2. WHERE OBJECT_ID = OBJECT_ID('Sales.SalesOrderDetail')
  3. AND name = 'FI_SpecialOfferID' )
  4. DROP INDEX Sales.SalesOrderDetail.FI_SpecialOfferID 
  5. GO

  6. CREATE INDEX FI_SpecialOfferID
  7. ON Sales.SalesOrderDetail (SpecialOfferID)
  8. WHERE SpecialOfferID<>1;
複製代碼


從代碼中可以看出:定義過濾的索引的語法和之前的定義索引的語法沒有什麼差別,只是多了個Where條件。


在代碼中,我們依然採用的示例數據庫AdventureWorks中的SalesOrderDetail'表。如果瞭解索引定義的一般規格的朋友們都知道:如果想在某個字段上面建立一個索引,一定要考慮這個字段的選擇率高不高(選擇率簡單的講就是字段中包含的唯一的值佔字段所有值的比例)。


通過分析數據,我們可以得到SpecialOfferID字段的一個數據的分佈情況,如下:



其實可以看得出,雖然表中包含了121,317行數據,其實在這些數據中,SpecialOfferID的唯一的值也是有16個,即從116,選擇率很低爲:(16/121317)*100%。即使我們在上面建立索引,那麼查詢優化器最後可能會進行過濾這個字段的時候進行整表的掃描,而不會使用建立的索引。


並且,在上面的數據中,SpecialOfferID值爲1的數據行佔所有數據行95%。所以,就算我們建立了索引,但是會進行整表掃描。朋友們就要問了:如果在查詢中的過濾條件是SpecialOfferID=5,或者是其他的但不是1的值,還是會進行整表的掃描嗎?回答:是的。


因爲現在的情況是:在索引的結構中,每一個數據行都要一個條目與之對應,也就是說,索引中包含很多的重複的數據,那麼優化器會認爲:與其先掃索引,在查找 數據表,還不如直接掃描數據表來的高效,事實也確實如此。


朋友們,就要問了:憑什麼SpecialOfferID值爲1的數據行,影響了其他的數據行呢,如果拋開值爲1的數據,還有5433行數據,此時的SpecialOfferID選擇率就是(11/5433)*100%,雖然不是很高,還沒有達到一些建議的值(75%以上,當然,要酌情考慮的,不是一個定死的值),但是比之前高多了?或者說,如果不存在了SpecialOfferID1的數據,只存在其他的,如果再在上面上面索引,會不會好些呢,這就是我們現在要講 的過濾的索引的作用:在建立索引的時候,把SpecialOfferID1的數據不要,而是對其他的值建立索引。


所以我們上面的定義過濾的索引的語句就可以這樣的解讀:建立索引的時候,如果遇到SpecialOfferID值爲1的數據行,那麼就不把這一行上面的SpecialOfferID值包含在索引中,否則,就包含。


不僅僅只有這個例子,過濾的索引的作用是非常的大的。例如,在我們的某些應用程序的數據庫中,當我們要在某些字段上面建立索引的時候,發現有一些值是null,此時,我們就可以過濾掉這個null等。



通過示例演示過濾的索引的高效性


光說不練可不行,我們就來看個例子。在下面的演示中,我們將會運行6個查詢:

1.在沒有建立過濾的索引的前提下,將過濾的參數分別設置爲1,13,14,然後各自運行一次。

2.在建立過濾索引的前提下,將參與設置爲1,13,14,然後在各自運行一次。


查詢語句的模板如下:

  1. SELECT *
  2. FROM Sales.SalesOrderDetail
  3. WHERE SpecialOfferID = <parameter value>
  4. ORDER BY SpecialOfferID ;
複製代碼


其實我們通過之前的數據分佈,知道查詢的結果的數據的比例:值爲1的,佔了95%,值爲13的,佔了4%,而值爲14的,佔2%

通過運行之後,查看實際的執行計劃,得到相關的信息,整理如下:


可以看出:從執行計劃看出,前後的成本是一樣的。但是其實有着巨大的差別:就是索引結構存儲所需要的磁盤空間,以及把索引結構讀入到內存,內存的佔用量,以及讀索引數據到內存中的時候的鎖資源的使用。使用過濾的索引,在這方面都會很小。


過濾的索引進階


在上面過濾的索引的示例中,索引中的數據列和過濾列是一樣的,如,我們在SpecialOfferID上面建立索引,同時也過濾掉SpecialOfferID不爲1的數據行。


其實,我們可以將之使用的更加靈活。例如,我們可以在SpecialOfferID上面建立索引,但是按照其他的數據列的值進行過濾。如下代碼所示:

  1. CREATE NONCLUSTERED INDEX FK_ProductID_ModifiedDate
  2. ON Sales.SalesOrderDetail (ProductID,ModifiedDate)
  3. INCLUDE (OrderQty,UnitPrice,LineTotal)
  4. WHERE SpecialOfferID <>1
複製代碼


然後運行一個查詢:

  1. SELECT ProductID ,
  2. ModifiedDate ,
  3. SUM(OrderQty) 'No of Items' ,
  4. AVG(UnitPrice) 'Avg Price' ,
  5. SUM(LineTotal) 'Total Value'
  6. FROM Sales.SalesOrderDetail
  7. WHERE SpecialOfferID <> 1
  8. GROUP BY ProductID ,
  9. ModifiedDate
複製代碼


運行上面的查詢就可以知道,通過使用過濾的索引之後,查詢只是掃描了36個索引頁(因爲這個所有的數據都包含在索引結構中了,沒有必要掃描底層的數據表),就找出所需要的2012行數據。



使用過濾的索引常見誤區


工具好了,有人一興奮,肯定會有人會亂用的,最後用的不好,就反過來怪工具不好,其實說到底,還是人,因爲工具是死的,人是活的。就好比有人在用.NET開發應用一樣,出來的東西性能很差,常常掛掉,然後就怪微軟不行,認爲.NET太差。出現問題之後,第一反應就是推卸責任,找替死鬼,這是人之常情,但是仔細想想到底是誰之過呢!所以,希望朋友們不要成爲“怨婦“,”怨夫“,把精力放在解決問題之上。

言歸正傳,下面,我們就來看看在使用過濾的索引的時候,必須注意哪些地方。



過濾的索引的條件定義問題


在上面的定義過濾的索引的代碼中,採用了Where來進行定義。這個時候,我們可能就會發散思維:如果我們只要索引中包含有SpecialOfferID2的值,是否可以把條件從“SpecialOfferID <>1“改爲”SpecialOfferID =2“。回答是:NO


原因就是在創建過濾的索引中使用的Where的過濾條件不是一個布爾的邏輯表達式,而是一個基於詞彙的表達式(大家只要知道這些就行了)。所以,我們也不能在Where中進行過條件的拼接,如”WHERE SpecialOfferID <> 1 AND SpecialOfferID = 2“。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章