過濾索引(Filtered index )是在SQL Server 2008里新引入的功能。到目前我們談到的索引都是在建立在整張表上的。換句話說,索引和表有一樣的記錄樹。使用過濾索引,我們可以創建表子集的索引。這個可以通過創建索引的時候加上where子語完成。這個可以幫助在存儲上減小索引的大小同樣索引的深度。在索引創建語句的where條件決定了索引里是否包含該記錄。
這在大表上是一個巨大的性能提升,如果有大量的查詢只查表的部分數據。常規索引都是建立在整個表上的,而忽略了大多數查詢只查表的一部分數據的事實。這樣話常規索引會增加索引的深度,需要更多的頁來存儲B樹結構,從而帶來更多的IO操作。而過濾索引只在表的部分數據上創建,因此需要更少的頁來存儲索引。
我們以有5年數據的salesorder 表來舉例。這個表上大多數活躍的查詢是基於去年和今年的。過濾索引的簡單例子如下:
1 CREATE NONCLUSTERED INDEX ix_salesorder_Filter 2 ON salesorder(SalesOrderId,OrderDate,Status,Customer_id,TotalDue) 3 WHERE OrderDate>'2014-01-01'
有多個NULL值的唯一列:
過濾索引的一個大量使用是在列上復合唯一限制定義。這個唯一限制,不允許在唯一列上有多個null值。列上的值除去NULL值,怎么保證它其它值唯一?例如,product表上的productode列可以為null,但是那列有值定義的話,就不能為null。我們來看一個例子。
1 CREATE TABLE Product 2 ( 3 Productid INT NOT NULL PRIMARY KEY , 4 ProductCode CHAR(10) , 5 ProductName VARCHAR(100) 6 ) 7 GO 8 CREATE UNIQUE INDEX ix_Unique_Filtered ON Product(Productcode) WHERE productcode IS NOT NULL 9 GO 10 INSERT INTO Product VALUES(1,'AR-5381','Adjustable Race') 11 INSERT INTO Product VALUES(2,NULL,'Bearing Ball') 12 INSERT INTO Product VALUES(3,NULL,'BB Ball Bearing') 13 INSERT INTO Product VALUES(4,'AR-5381','Adjustable Race-Small')

我們可以插入produtcode列為NULL的多條記錄,但當我們嘗試插入有重復值AR-5381的記錄是,SQL Server提示錯誤,那是因為表上的唯一過濾索引幫助我們強制這樣的唯一性。
讀操作:
上述提高的是覆蓋索引的一種常規用法。覆蓋索引的另一種用法是支持對應的查詢,我們來看一個例子,點擊工具欄的
顯示包含實際的執行計划。
1 USE IndexDB 2 GO 3 SELECT * INTO SalesOrderheader FROM AdventureWorks2008r2.Sales.SalesOrderheader 4 GO 5 --Unique Clustered index 6 CREATE UNIQUE CLUSTERED INDEX ix_SalesOrderheader ON SalesOrderheader(SalesOrderid) 7 GO 8 --Filtered Index 9 CREATE INDEX ix_filtered_index ON SalesOrderheader(orderdate) WHERE orderdate>'2008-01-01' 10 11 SELECT orderdate,SalesOrderid FROM SalesOrderheader WHERE 12 orderdate>'2008-05-01' 13 GO 14 SELECT orderdate,SalesOrderid FROM SalesOrderheader WHERE 15 orderdate='2008-03-01' 16 GO 17 SELECT orderdate,SalesOrderid FROM SalesOrderheader WHERE 18 orderdate='2007-12-01'

我們在orderdate列上定義了過濾索引,條件是日期大於2008-1-1。第一個和第二個查詢條件都命中了過濾索引的條件,因此都用到了索引查找;第三個在覆蓋索引條件之外,SQL Server只能用聚集索引掃描來找對應的數據。過濾索引對於這類查詢有大幅度的性能提升。
過濾列並不一定是索引的一部分。但如果是這個情況的話,查詢條件就要和過濾索引的條件完全一致了,我們來看一個例子。
1 CREATE INDEX ix_TerritoryID_Filter ON SalesOrderheader (OrderDate) WHERE TerritoryID<=5 2 GO 3 SELECT salesorderid,orderdate FROM SalesOrderheader WHERE 4 TerritoryID<=5 5 GO 6 SELECT salesorderid,orderdate FROM SalesOrderheader WHERE 7 TerritoryID=4

過濾索引局限性:
過濾索引非常有用,但也有它的局限性,尤其是在參數化查詢的時候。我們來看一個例子。
1 SELECT orderdate,SalesOrderid FROM SalesOrderheader WHERE orderdate>'2008-05-01' 2 3 DECLARE @Orderdate date='2008-05-01' 4 SELECT orderdate,SalesOrderid FROM SalesOrderheader WHERE orderdate>@Orderdate

當我們用本地參數重寫我們的查詢的時候,查詢計划沒有用到過濾索引。背后的原因是,在編譯期間,查詢優化器不知道什么樣的值會傳給@Orderdate參數。因此優化器生成一個保守的計划來滿足所有的條件。當我們修改數據庫屬性來強制參數化或把語句定義為存儲過程也會出現同樣的問題。
1 ALTER DATABASE IndexDB SET parameterization forced 2 GO 3 SELECT orderdate,SalesOrderid FROM SalesOrderheader WHERE orderdate>'2008-05-01' 4 GO 5 6 CREATE PROCEDURE GetSalesorder (@OrderDate date) 7 AS 8 BEGIN 9 SELECT orderdate,SalesOrderid FROM SalesOrderheader WHERE orderdate=@OrderDate END 10 11 12 EXEC GetSalesorder '2008-05-01'
在這些情況下優化器都沒有用到過濾索引,因為它在執行的時候不知道傳入的值是多少,只能不考慮使用過濾索引生成一個相對保守的計划。
當我們把數據庫屬性設置為強制參數化時(ALTER DATABASE IndexDB SET parameterization forced),優化器會把所有靜態值用本地變量代替。例如下面的語句:
1 SELECT orderdate,SalesOrderid FROM SalesOrderheader WHERE orderdate>'2008-05-01'
優化器會認為是下列語句:
1 DECLARE @Orderdate date='2008-05-01' 2 SELECT orderdate,SalesOrderid FROM SalesOrderheader WHERE orderdate>@Orderdate
但在動態生成的語句時,會讓SQL Server用到過濾索引:
1 ALTER DATABASE IndexDB SET PARAMETERIZATION SIMPLE 2 3 DECLARE @Orderdate date='2008-05-01' 4 DECLARE @SQL NVARCHAR(1000) 5 SET @SQL=N'SELECT orderdate,SalesOrderid FROM SalesOrderheader WHERE 6 orderdate>'''+CAST(@Orderdate AS CHAR(10))+'''' 7 EXEC (@SQL) 8 GO

從執行計划里我們可以看到,查詢計划用到了過濾索引。
