在计划指南中使用 INDEX 和 FORCESEEK 查询提示

您可以将 INDEX 和 FORCESEEK 表提示指定为查询提示。如果将这些提示指定为查询提示,则它们与内联表或视图提示的行为方式相同。

INDEX 提示强制查询优化器在查询中仅使用指定的索引来访问被引用表或视图中的数据。FORCESEEK 提示强制优化器仅使用索引查找操作来访问被引用表或视图中的数据。可以在计划指南的 OPTION 子句中使用这些提示,以影响查询的优化程度。当查询与计划指南匹配时,在查询进行编译和优化之前,计划指南中指定的 OPTION 子句将添加到查询中。有关计划指南的详细信息,请参阅了解计划指南

注意事项注意

计划指南错用查询提示会导致出现编译、执行或性能问题。计划指南应仅由经验丰富的开发人员和数据库管理员使用。

如果将 INDEX 和 FORCESEEK 表提示指定为查询提示,则它们对以下对象有效:

  • 视图

  • 索引视图

  • 公用表表达式(提示必须在其结果集填充公用表表达式的 SELECT 语句中指定。)

  • 动态管理视图

  • 命名子查询

不能为表值函数、表变量或 OPENROWSET 语句指定表提示。

若要为索引视图指定一个索引提示,还必须在 OPTION 子句中指定 NOEXPAND 提示,否则将忽略该索引提示。有关详细信息,请参阅解析视图的索引

有关用于将 INDEX 和 FORCESEEK 提示指定为查询提示的语法的信息,请参阅查询提示 (Transact-SQL)

最佳实践

以下为建议的最佳实践:

  • 在测试计划指南语句时,仅在计划指南的上下文中或即席查询中将 INDEX 和 FORCESEEK 提示用作查询提示。对于所有其他即席查询,请将这些提示指定为表提示。

  • 在使用 FORCESEEK 提示之前,请确保有关数据库的统计信息是最新的,且准确无误。

    利用最新的统计信息,优化器能准确地估算不同查询计划的开销,从而能选择高质量的计划。因此,建议对于每个用户数据库,都将 AUTO_CREATE_STATISTICS 和 AUTO_UPDATE_STATISTICS 设置为 ON(默认值)。或者,您可以使用 UPDATE STATISTICS 语句手动更新表或视图的统计信息。

  • 除非在必要情况下,否则请不要将 INDEX 提示与 FORCESEEK 一起使用。也就是说,如果单独使用 FORCESEEK 就可生成满足需要的计划,则同时使用 INDEX 提示会过分限制优化器可做的选择。而且,在更改表的物理架构以删除提示中指定的索引时,INDEX 提示将导致查询失败。相比之下,只要应用 FORCESEEK 提示的表中至少存在一个可使用的索引,则即使在您更改索引结构时查询仍能编译。

  • 请不要将 INDEX 提示 INDEX (0) 与 FORCESEEK 提示一起使用。INDEX (0) 强制对基表进行扫描。当与 FORCESEEK 同时使用时,将找不到计划,并返回错误 8622。

  • 请不要将 USE PLAN 查询提示与 FORCESEEK 提示一起使用。否则,将忽略 FORCESEEK 提示。

将 INDEX 和 FORCESEEK 提示与其他表提示一起使用

可以为没有任何现有表提示的查询指定 INDEX 和 FORCESEEK 提示,这些提示也可用于替换查询中一个或多个现有 INDEX 和 FORCESEEK 提示。如果与计划指南匹配的查询已经含有一个指定上述表提示的 WITH 子句,则在计划指南的 @hints 参数中指定的查询提示会替换查询中的这些提示。例如,如果查询包含 HumanResources.Employee 表的表提示 WITH INDEX (PK_Employee_EmployeeID),而计划指南中的 @hints 参数指定 OPTION (TABLE HINT ( HumanResources.Employee, INDEX( IX_Employee_ManagerID ) ),则查询优化器将使用 IX_Employee_ManagerID 索引。

不允许非 INDEX 和 FORCESEEK 的表提示用作计划指南中的查询提示,除非该查询已经具有一个指定表提示的 WITH 子句。这种情况下,还必须使用 OPTION 子句中的 TABLE HINT 来将匹配的提示指定为查询提示,以保留查询的语义。例如,如果查询包含表提示 NOLOCK,则计划指南的 @hints 参数中的 OPTIONS 子句除了包含 OPTIONS 子句中的任意 INDEX 或 FORCESEEK 表提示外,还必须包含 NOLOCK 提示。请参阅本主题后面的示例 C。当通过使用 OPTION 子句中的 TABLE HINT 指定了非 INDEX 或 FORCESEEK 的表提示,而未指定匹配的查询提示时,或指定了后者而未指定前者,则会引发错误 8702,表示 OPTION 子句会导致查询的语义发生变化,该查询将失败。

将 INDEX 和 FORCESEEK 提示与其他查询提示一起使用

如果与计划指南匹配的查询已经含有一个指定查询提示的 OPTION 子句,则在计划指南的 @hints 参数中指定的查询提示会替换查询中的这些提示。但是,若要使计划指南与已含有 OPTION 子句的查询匹配,则您在 sp_create_plan_guide (Transact-SQL) 语句中指定要匹配的查询的文本时,必须包含该查询的 OPTION 子句。若要将计划指南中指定的提示添加到查询中已存在的提示,而不是替换已存在的提示,则必须在计划指南的 OPTION 子句中同时指定原始提示和附加提示。

示例

A. 使用 FORCESEEK

下例在计划指南的 @hints 参数中使用 FORCESEEK 提示。此选项强制优化器使用索引查找操作来访问 HumanResources.Employee 表中的数据。请注意,这可能会导致优化器使用一个并非在表提示中指定的索引。

USE AdventureWorks;
GO
EXEC sp_create_plan_guide 
    @name = N'Guide3', 
    @stmt = N'SELECT c.LastName, c.FirstName, HumanResources.Employee.Title
              FROM HumanResources.Employee
              JOIN Person.Contact AS c ON HumanResources.Employee.ContactID = c.ContactID
              WHERE HumanResources.Employee.ManagerID = 3
              ORDER BY c.LastName, c.FirstName;', 
    @type = N'SQL',
    @module_or_batch = NULL, 
    @params = NULL, 
    @hints = N'OPTION (TABLE HINT( HumanResources.Employee, FORCESEEK))';
GO

B. 使用多个表提示

下例将 INDEX 提示应用到一个表,将 FORCESEEK 提示应用到另一个表。

USE AdventureWorks;
GO
EXEC sp_create_plan_guide 
    @name = N'Guide4', 
    @stmt = N'SELECT e.ManagerID, c.LastName, c.FirstName, e.Title
              FROM HumanResources.Employee AS e 
              JOIN Person.Contact AS c ON e.ContactID = c.ContactID
              WHERE e.ManagerID = 3;', 
    @type = N'SQL',
    @module_or_batch = NULL, 
    @params = NULL, 
    @hints = N'OPTION (TABLE HINT ( e, INDEX( IX_Employee_ManagerID ) ) 
                       , TABLE HINT ( c, FORCESEEK) )';
GO

C. 指定影响语义的提示

下例在查询中包含两个表提示:NOLOCK 和 INDEX,前者为影响语义的提示,后者为不影响语义的提示。若要保留查询的语义,应在计划指南的 OPTIONS 子句中指定 NOLOCK 提示。除 NOLOCK 提示外,还指定了 INDEX 和 FORCESEEK 提示,它们在编译和优化语句时将替换查询中不影响语义的 INDEX 提示。

USE AdventureWorks;
GO
EXEC sp_create_plan_guide 
    @name = N'Guide6', 
    @stmt = N'SELECT c.LastName, c.FirstName, e.Title
              FROM HumanResources.Employee AS e 
                   WITH (NOLOCK, INDEX (PK_Employee_EmployeeID))
              JOIN Person.Contact AS c ON e.ContactID = c.ContactID
              WHERE e.ManagerID = 3;',
    @type = N'SQL',
    @module_or_batch = NULL, 
    @params = NULL, 
    @hints = N'OPTION (TABLE HINT ( e, INDEX( IX_Employee_ManagerID) , NOLOCK, FORCESEEK ))';
GO

下例显示了另一种保留查询语义并使优化器能够选择并非在表提示中指定的索引的方法。此方法是这样实现的:在 OPTIONS 子句中指定 NOLOCK 提示(因为它是影响语义的),并指定只带有一个表引用、不带 INDEX 提示的 TABLE HINT 关键字。

USE AdventureWorks;
GO
EXEC sp_create_plan_guide 
    @name = N'Guide7', 
    @stmt = N'SELECT c.LastName, c.FirstName, e.Title
              FROM HumanResources.Employee AS e 
                   WITH (NOLOCK, INDEX (PK_Employee_EmployeeID))
              JOIN Person.Contact AS c ON e.ContactID = c.ContactID
              WHERE e.ManagerID = 2;',
    @type = N'SQL',
    @module_or_batch = NULL, 
    @params = NULL, 
    @hints = N'OPTION (TABLE HINT ( e, NOLOCK))';
GO

D. 使用 TABLE HINT 覆盖现有的表提示

下例显示如何在不指定 INDEX 提示的情况下使用 TABLE HINT 覆盖在查询的 FROM 子句中指定的 INDEX 表提示的行为。此方法使优化器能选择并非在表提示中指定的索引。

USE AdventureWorks;
GO
EXEC sp_create_plan_guide 
    @name = N'Guide5', 
    @stmt = N'SELECT e.ManagerID, c.LastName, c.FirstName, e.Title
              FROM HumanResources.Employee AS e WITH (INDEX (IX_Employee_ManagerID))
              JOIN Person.Contact AS c ON e.ContactID = c.ContactID
              WHERE e.ManagerID = 3;', 
    @type = N'SQL',
    @module_or_batch = NULL, 
    @params = NULL, 
    @hints = N'OPTION (TABLE HINT(e))';
GO