篩選索引設計準則

篩選索引是一種通過優化的非彙集索引,尤爲適用於涵蓋從定義完善的數據子集中選擇數據的查詢。篩選索引使用篩選謂詞對錶中的部分行進行索引。與全表索引相比,設計良好的篩選索引能夠提升查詢性能、減小索引維護開銷並可下降索引存儲開銷。 sql

篩選索引與全表索引相比具備如下優勢: 數據庫

  • 提升了查詢性能和計劃質量 express

    設計良好的篩選索引能夠提升查詢性能和執行計劃質量,由於它比全表非彙集索引小而且具備通過篩選的統計信息。與全表統計信息相比,通過篩選的統計信息更加準確,由於它們只涵蓋篩選索引中的行。 工具

  • 減小了索引維護開銷 性能

    僅在數據操做語言 (DML) 語句對索引中的數據產生影響時,纔對索引進行維護。與全表非彙集索引相比,篩選索引減小了索引維護開銷,由於它更小而且僅在對索引中的數據產生影響時才進行維護。篩選索引的數量能夠很是多,特別是在其中包含不多受影響的數據時。一樣,若是篩選索引只包含頻繁受影響的數據,則索引大小較小時能夠減小更新統計信息的開銷。 優化

  • 減小了索引存儲開銷 spa

    在不必建立全表索引時,建立篩選索引能夠減小非彙集索引的磁盤存儲開銷。可使用多個篩選索引替換一個全表非彙集索引而不會明顯增長存儲須要。 .net

爲了設計有效的篩選索引,必須瞭解應用程序使用哪些查詢以及這些查詢與您的數據子集有何關聯。例如,所含值中大部分爲 NULL 的列、含異類類別的值的列以及含不一樣範圍的值的列都屬於具備定義完善的子集的數據。如下設計注意事項提供了篩選索引優於全表索引的各類狀況。 設計

數據子集的篩選索引

在列中只有少許相關值須要查詢時,能夠針對值的子集建立篩選索引。例如,當列中的值大部分爲 NULL 而且查詢只從非 NULL 值中進行選擇時,能夠爲非 NULL 數據行建立篩選索引。由此獲得的索引與對相同鍵列定義的全表非彙集索引相比,前者更小且維護開銷更低。 code

例如,AdventureWorks 數據庫中有一個包含 2679 行的 Production.BillOfMaterials 表。EndDate 列只有 199 行包含非 NULL 值,其他 2480 行均包含 NULL。下面的篩選索引將涵蓋這樣的查詢:返回在此索引中定義的列的查詢,以及只選擇 EndDate 值不爲 NULL 的行的查詢。

USE AdventureWorks;
GO
IF EXISTS (SELECT name FROM sys.indexes
    WHERE name = N'FIBillOfMaterialsWithEndDate'
    AND object_id = OBJECT_ID (N'Production.BillOfMaterials'))
DROP INDEX FIBillOfMaterialsWithEndDate
    ON Production.BillOfMaterials
GO
CREATE NONCLUSTERED INDEX FIBillOfMaterialsWithEndDate
    ON Production.BillOfMaterials (ComponentID, StartDate)
    WHERE EndDate IS NOT NULL ;
GO

篩選索引 FIBillOfMaterialsWithEndDate 對下面的查詢有效。您能夠顯示查詢執行計劃,以肯定查詢優化器是否使用了此篩選索引。有關如何顯示查詢執行計劃的信息,請參閱分析查詢

SELECT ProductAssemblyID, ComponentID, StartDate 
FROM Production.BillOfMaterials
WHERE EndDate IS NOT NULL 
    AND ComponentID = 5 
    AND StartDate > '01/01/2008' ;
GO

有關如何建立篩選索引以及如何定義篩選索引謂詞表達式的詳細信息,請參閱 CREATE INDEX (Transact-SQL)

異類數據的篩選索引

表中含有異類數據行時,能夠爲一種或多種類別的數據建立篩選索引。

例如,Production.Product 表中列出的每種產品均分配到一個 ProductSubcategoryID,後者又與 Bikes、Components、Clothing 或 Accessories 產品類別關聯。這些類別爲異類類別,由於它們在 Production.Product 表中的列值並非緊密相關的。例如,對於每種產品類別,Color、ReorderPoint、ListPrice、Weight、Class 和 Style 均具備惟一特徵。假設會常常查詢具備子類別 27-36 的 Accessories。經過對 Accessories 子類別建立篩選索引,能夠提升對 Accessories 的查詢的性能。

下面的示例對 Production.Product 表中 Accessories 子類別中的全部產品建立一個篩選索引。

USE AdventureWorks;
GO
IF EXISTS (SELECT name FROM sys.indexes
    WHERE name = N'FIProductAccessories'
    AND object_id = OBJECT_ID ('Production.Product'))
DROP INDEX FIProductAccessories
    ON Production.Product;
GO
CREATE NONCLUSTERED INDEX FIProductAccessories
    ON Production.Product (ProductSubcategoryID, ListPrice) 
        Include (Name)
WHERE ProductSubcategoryID >= 27 AND ProductSubcategoryID <= 36;
GO

篩選索引 FIProductAccessories 涵蓋下面的查詢,由於查詢結果包含在該索引中,而且查詢計劃不包括基表查找。例如,查詢謂詞表達式 ProductSubcategoryID = 33 是篩選索引謂詞 ProductSubcategoryID >= 27 和 ProductSubcategoryID <= 36 的子集,查詢謂詞中的 ProductSubcategoryID 和 ListPrice 列全都是索引中的鍵列,而且名稱做爲包含列存儲在索引的葉級別。

SELECT Name, ProductSubcategoryID, ListPrice
FROM Production.Product
WHERE ProductSubcategoryID = 33 AND ListPrice > 25.00 ;
GO

視圖與篩選索引

視圖是存儲查詢定義的虛擬表;與篩選索引相比,其用途更廣,功能更強。有關視圖的詳細信息,請參閱瞭解視圖使用視圖的狀況。下表比較了在視圖和篩選索引中可使用的部分功能。

在表達式中容許

視圖

篩選的索引

計算列

聯接

多個表

謂詞中的簡單比較邏輯*

謂詞中的複雜邏輯**

*有關謂詞中的簡單比較邏輯,請參閱 CREATE INDEX 中的 WHERE 子句語法。

**有關謂詞中的複雜比較邏輯,請參閱 SELECT 中的 WHERE 子句語法。

不能對視圖建立篩選索引。可是,查詢優化器能夠從對視圖中引用的表定義的篩選索引中獲益。對於從視圖中選擇數據的查詢,若是查詢結果正確,查詢優化器會考慮對此查詢使用篩選索引。下面的示例建立一個視圖(開始日期在 2000 年 4 月 1 日之後)和一個篩選索引(開始日期在 2000 年 8 月 1 日之後)。

SQL
USE AdventureWorks;
GO
IF OBJECT_ID ('ViewOnBillOfMaterials') IS NOT NULL
DROP VIEW ViewOnBillOfMaterials;
GO
CREATE VIEW ViewOnBillOfMaterials AS 
SELECT ComponentID, StartDate, EndDate, StartDate + 2 AS ShipDate
FROM Production.BillOfMaterials
WHERE StartDate > '20000401';
GO
IF EXISTS (SELECT name FROM sys.indexes
    WHERE name = N'FIBillOfMaterialsByStartDate'
    AND object_ID = OBJECT_ID (N'Production.BillOfMaterials'))
DROP INDEX FIBillOfMaterialsByStartDate 
    ON Production.BillOfMaterials;
GO
CREATE NONCLUSTERED INDEX FIBillOfMaterialsByStartDate
    ON Production.BillOfMaterials (ComponentID, StartDate, EndDate)
WHERE StartDate > '20000801';
GO

在下面的示例中,查詢選擇 2000 年 9 月 1 日之後的開始日期,這些日期徹底包含在此篩選索引和篩選視圖中。查詢優化器將考慮使用篩選索引 FIBillOfMaterialsByStartDate,由於其中包含了正確的查詢結果。

SELECT StartDate, ComponentID FROM ViewOnBillOfMaterials
WHERE StartDate > '20000901';
GO

在下一示例中,查詢選擇 2000 年 6 月 1 日之後的開始日期,這些日期徹底包含在此視圖中,但未徹底包含在此篩選索引中。查詢優化器不考慮使用篩選索引 FIBillOfMaterialsByStartDate,由於與查詢從視圖中選擇數據所返回的正確結果相比,查詢使用篩選索引會返回不一樣的結果。

SELECT StartDate, ComponentID FROM ViewOnBillOfMaterials
WHERE StartDate > '20000601';
GO

索引視圖與篩選的索引的對比

篩選的索引與索引視圖相比,具備如下優勢:

  • 減小了索引維護開銷。例如,相對於索引視圖而言,查詢處理器使用更少的 CPU 資源即可更新篩選的索引。

  • 改善了計劃質量。例如,在查詢編譯期間,查詢優化器考慮使用篩選的索引的狀況要比考慮使用等效的索引視圖的狀況多。

  • 聯機索引從新生成。您能夠在篩選的索引可用於查詢時從新生成它們。索引視圖不支持聯機索引從新生成。有關詳細信息,請參閱 ALTER INDEX (Transact-SQL) 的 REBUILD 選項。

  • 非惟一索引。篩選的索引能夠是非惟一的,而索引視圖必須是惟一的。

出於以上緣由,建議儘量使用篩選的索引,不使用索引視圖。若是知足如下條件,則可使用篩選的索引而不使用索引視圖:視圖只引用一個表,查詢不返回計算列且視圖謂詞使用簡單的比較邏輯。例如,容許在視圖定義中使用以下謂詞表達式,但不容許在篩選索引中使用它,由於它包含 LIKE 運算符。

WHERE StartDate > '20000701' AND ModifiedDate LIKE 'E%'

鍵列

最好在篩選索引定義中包含少許的鍵或包含列,而且只包含查詢優化器爲查詢執行計劃選擇篩選索引所需的列。不管某一篩選索引是否涵蓋了查詢,查詢優化器均可覺得查詢選擇此篩選索引。可是,若是某一篩選索引涵蓋了查詢,則查詢優化器更有可能選擇此篩選索引。有關涵蓋查詢的詳細信息,請參閱建立帶有包含列的索引

在某些狀況下,篩選索引涵蓋查詢,但沒有將篩選索引表達式中的列做爲鍵或包含列包括在篩選索引定義中。如下準則說明了篩選索引表達式中的列什麼時候應爲篩選索引定義中的鍵或包含列。這些示例引用了此前建立的篩選索引 FIBillOfMaterialsWithEndDate。

若是篩選索引表達式等效於查詢謂詞而且查詢並未在查詢結果中返回篩選索引表達式中的列,則篩選索引表達式中的列不須要做爲篩選索引定義中的鍵或包含列。例如,FIBillOfMaterialsWithEndDate 涵蓋下面的查詢,由於查詢謂詞等效於篩選表達式,而且查詢結果中未返回 EndDate。FIBillOfMaterialsWithEndDate 不須要將 EndDate 做爲篩選索引定義中的鍵或包含列。

SELECT ComponentID, StartDate FROM Production.BillOfMaterials
WHERE EndDate IS NOT NULL;
GO

若是查詢謂詞在不與篩選索引表達式等效的比較中使用了篩選索引表達式中的某列,則該列應爲篩選索引定義中的鍵或包含列。例如,FIBillOfMaterialsWithEndDate 對下面的查詢有效,由於它從篩選索引中選擇了行的子集。可是,它不涵蓋下面的查詢,由於在比較 EndDate > '20000101' 中使用了 EndDate,此比較不與篩選索引表達式等效。查詢處理器在不查找 EndDate 值的狀況下沒法執行此查詢。所以,EndDate 應爲篩選索引定義中的鍵或包含列。

SELECT ComponentID, StartDate FROM Production.BillOfMaterials
WHERE EndDate > '20000101';
GO

若是篩選索引表達式中的某列在查詢結果集中,則該列應爲篩選索引定義中的鍵或包含列。例如,FIBillOfMaterialsWithEndDate 不涵蓋下面的查詢,由於它在查詢結果中返回了 EndDate 列。所以,EndDate 應爲篩選索引定義中的鍵或包含列。

SELECT ComponentID, StartDate, EndDate FROM Production.BillOfMaterials
WHERE EndDate IS NOT NULL;
GO

表的主鍵不須要是篩選索引定義中的鍵或包含列。主鍵自動包含在全部非彙集索引(包括篩選索引)中。

篩選謂詞中的數據轉換運算符

若是篩選索引結果的篩選索引表達式中指定的比較運算符會致使隱式或顯式數據轉換,則轉換髮生在比較運算符的左邊時,會出現錯誤。解決方法是在比較運算符的右邊編寫包含數據轉換運算符(CAST 或 CONVERT)的篩選索引表達式。

下面的示例建立一個包含多種數據類型的表。

SQL
USE AdventureWorks;
GO
IF OBJECT_ID ('dbo.TestTable') IS NOT NULL
DROP TABLE dbo.TestTable;
GO
CREATE TABLE TestTable (a int, b varbinary(4));
GO

在下面的篩選索引定義中,列 b 隱式轉換爲整數數據類型,以便與常量 1 進行比較。由於轉換髮生在篩選謂詞中運算符的左邊,因此這會生成錯誤消息 10611。

USE AdventureWorks;
GO
IF EXISTS ( SELECT name from sys.indexes 
    WHERE name = N'TestTabIndex'
    AND object_id = OBJECT_ID (N'dbo.TestTable'))
DROP INDEX TestTabIndex on dbo.TestTable
GO
CREATE NONCLUSTERED INDEX TestTabIndex ON dbo.TestTable(a,b)
WHERE b = 1;
GO

解決方法是將右側的常量轉換爲與列 b 的類型相同的類型,以下例所示:

CREATE INDEX TestTabIndex ON dbo.TestTable(a,b)
WHERE b = CONVERT(Varbinary(4), 1);
GO

將數據轉換從比較運算符的左邊移動到右邊可能會改變轉換的含義。在上例中,將 CONVERT 運算符添加到右邊時,相應的比較從整數比較更改成 varbinary 比較。

引用依賴項

sys.sql_expression_dependencies 目錄視圖將篩選索引表達式中的每一列做爲一個引用依賴項進行跟蹤。不能刪除、重命名或更改在篩選索引表達式中定義的表列的定義。

什麼時候使用篩選索引

列中包含查詢在 SELECT 語句中引用的定義完善的數據子集時,篩選索引頗有用。如下是一些示例:

  • 僅包含少許非 NULL 值的稀疏列。

  • 包含多種類別的數據的異類列。

  • 包含多個範圍的值(如美圓金額、時間和日期)的列。

  • 由列值的簡單比較邏輯定義的表分區。

若是索引中的行數與全表索引相比較少時,篩選索引減小的維護開銷最爲明顯。若是篩選索引包含表中的大部分行,則與全表索引相比,其維護開銷可能更高。在這種狀況下,應使用全表索引而不是篩選索引。

篩選索引是針對一個表定義的,僅支持簡單比較運算符。若是須要引用多個表或具備複雜邏輯的篩選表達式,則應建立視圖。

篩選索引功能支持

通常狀況下,數據庫引擎和工具爲篩選索引提供了與非彙集全表索引相同的支持,將篩選索引視爲特殊類型的非彙集索引。下面的列表提供了有關對篩選索引提供徹底支持、不提供支持或提供有限支持的工具和功能的說明。

  • ALTER INDEX 支持篩選索引。若要修改篩選索引表達式,請使用 CREATE INDEX WITH DROP_EXISTING。

  • 缺失索引功能不建議使用篩選索引。

  • 數據庫引擎優化顧問在提供索引優化建議時會考慮篩選索引,而且可能會建議 is not null 篩選索引。

  • 聯機索引操做支持篩選索引。

  • 表提示支持篩選索引,但有一些不適用於非篩選索引的限制。下一節將介紹這些內容。

若是不管是否使用篩選索引,查詢均選擇相同的結果,則查詢優化器會使用篩選索引。此前介紹的篩選索引 FIBillOfMaterialsWithEndDate 對如下兩個查詢有效。在第一個示例中,查詢謂詞與篩選索引謂詞 WHERE EndDate IS NOT NULL 徹底匹配。在第二個示例中,因爲查詢謂詞包含索引中行的子集,因此它比篩選謂詞具備更強的選擇性。

SELECT ComponentID, StartDate FROM Production.BillOfMaterials
WHERE EndDate IS NOT NULL;
GO
SELECT ComponentID, StartDate FROM Production.BillOfMaterials
WHERE EndDate < '20000701';
GO

下一個查詢也可以使用 FIBillOfMaterialsWithEndDate。可是,因爲存在其餘決定查詢開銷的因素(如查詢謂詞的選擇性),優化器可能不會選擇篩選索引。以下例所示,能夠經過將篩選索引用做查詢提示強制優化器選擇篩選索引。

SELECT ComponentID, StartDate FROM Production.BillOfMaterials
    WITH ( INDEX ( FIBillOfMaterialsWithEndDate ) )
WHERE EndDate IN ('20000825', '20000908', '20000918');
GO

若是查詢能夠返回不在篩選索引中的行,則查詢優化器將不會使用篩選索引。例如,查詢優化器將不考慮對下面的查詢使用 FIBillOfMaterialsWithEndDate,由於查詢可能返回 NULL EndDate 值和非 NULLModifiedDate 值,這些值不能包含在 FIBillOfMaterialsWithEndDate 中(由於它只包含非 NULL EndDate 值)。

SELECT ComponentID, StartDate FROM Production.BillOfMaterials
WHERE EndDate IS NOT NULL OR ModifiedDate IS NOT NULL;
GO

若是篩選索引顯式用做表提示且可能未包含全部查詢結果,則查詢優化器產生查詢編譯錯誤 8622。在下面的示例中,查詢優化器產生錯誤 8622,由於 FIBillOfMaterialsWithEndDate 對於查詢無效且它顯式用做索引提示:

SELECT StartDate, ComponentID FROM Production.BillOfMaterials
    WITH ( INDEX ( FIBillOfMaterialsWithEndDate ) )
WHERE EndDate IS NOT NULL OR ModifiedDate IS NOT NULL;
GO

參數化查詢

在某些狀況下,參數化查詢在編譯時包含的信息不足以知足查詢優化器選擇篩選索引的須要。可能能夠重寫此查詢以提供缺乏的信息。在下面的示例中,查詢優化器不考慮對 SELECT 語句使用篩選索引FIBillOfMaterialsWithComponentID,由於 @p 和 @q  的參數值在編譯時未知。下面的查詢示例運行時將 SHOWPLAN_XML 設置爲 ON,以使您能夠在 SHOWPLAN_XML 輸出中查看參數化查詢的不匹配的篩選索引。

SQL
USE AdventureWorks;
GO
IF EXISTS ( SELECT name FROM sys.indexes
    WHERE name = N'FIBillOfMaterialsWithComponentID'
    AND object_id = OBJECT_ID (N'Production.BillOfMaterials'))
DROP INDEX FIBillOfMaterialsWithComponentID
    ON Production.BillOfMaterials;
GO
CREATE NONCLUSTERED INDEX FIBillOfMaterialsWithComponentID
    ON Production.BillOfMaterials (ComponentID, StartDate, EndDate)
WHERE ComponentID IN (533, 324, 753);
GO
SET SHOWPLAN_XML ON;
GO
DECLARE @p AS INT, @q AS INT;
SET @p = 533;
SET @q = 324;
SELECT StartDate, ComponentID from Production.BillOfMaterials 
WHERE ComponentID = @p OR ComponentID = @q;
GO
SET SHOWPLAN_XML OFF;
GO

SHOWPLAN_XML 輸出中的 UnmatchedIndexes 元素和 Parameterization 子元素指示篩選索引與查詢不匹配。有關如何查看 SHOWPLAN_XML 輸出的信息,請參閱 XML 顯示計劃

解決方法是修改此查詢,使在參數化表達式不是篩選謂詞的子集時查詢結果爲空。下面的查詢說明了如何進行這種修改。經過將 ComponentID in (533, 324, 753) 表達式添加到 WHERE 子句,確保了此查詢的結果是篩選謂詞表達式的子集。經過這種修改,查詢優化器能夠考慮對下面的 SELECT 語句使用篩選索引 FIBillOfMaterialsWithComponentID。

USE AdventureWorks;
GO
SET SHOWPLAN_XML ON;
GO
DECLARE @p AS INT, @q AS INT;
SET @p = 533;
SET @q = 324;
SELECT StartDate, ComponentID FROM Production.BillOfMaterials
WHERE ComponentID in (533, 324, 753)
    AND (ComponentID = @p OR ComponentID = @q);
GO
SET SHOWPLAN_XML OFF;
GO

簡單參數化

在大多數狀況下,若是某查詢計劃包括篩選索引,查詢優化器將不對該查詢執行簡單參數化(在 SQL Server 2005 中稱爲「自動參數化」)。對此類查詢執行簡單參數化可擴大可能參數值的範圍,這樣,篩選索引便不能保證查詢結果的準確性。例如,若是 SELECT 語句的 WHERE 子句使用了在篩選索引的謂詞中使用的列,則查詢優化器可能不會執行簡單參數化,這是由於查詢計劃中極可能會包括篩選索引。

若是適合,使用本節中所述的準則重寫查詢以確保篩選索引將涵蓋該查詢,也許可以參數化該查詢。

使用鍵查找的查詢

查詢優化器能夠執行鍵查找操做來檢索篩選索引沒有涵蓋的剩餘列,從而即便在某篩選索引不涵蓋查詢的狀況下也可使用該篩選索引。有關鍵查找的詳細信息,請參閱Key Lookup Showplan 運算符。若是估計的鍵查找次數不多,則查詢優化器可能會選擇此方法。下面的查詢使用索引提示強制查詢處理器使用 FIBillOfMaterialsWithEndDate,同時對 EndDate 執行書籤查找操做。對於查詢謂詞中的 EndDate > @date 比較,會執行鍵查找操做。

USE AdventureWorks;
GO
DECLARE @date AS DATE;
SET @date = '20000825'
SELECT ComponentID, StartDate, EndDate FROM Production.BillOfMaterials
WITH ( INDEX (FIBillOfMaterialsWithEndDate) )
WHERE EndDate > @date;
GO

請注意,EndDate > @Date 與篩選索引表達式 EndDate IS NOT NULL 不徹底匹配。篩選索引對此參數化查詢仍有效,由於它返回了由篩選索引表達式定義的行的子集。

相關文章
相關標籤/搜索