筛选索引是一种通过优化的非汇集索引,尤为适用于涵盖从定义完善的数据子集中选择数据的查询。筛选索引使用筛选谓词对表中的部分行进行索引。与全表索引相比,设计良好的筛选索引能够提升查询性能、减小索引维护开销并可下降索引存储开销。 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 日之后)。
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)的筛选索引表达式。
下面的示例建立一个包含多种数据类型的表。
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 输出中查看参数化查询的不匹配的筛选索引。
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 不彻底匹配。筛选索引对此参数化查询仍有效,由于它返回了由筛选索引表达式定义的行的子集。