【问题标题】:SQL Server - Creating a Plan Guide without using an aliasSQL Server - 在不使用别名的情况下创建计划指南
【发布时间】:2017-11-08 13:48:41
【问题描述】:

我正在尝试在 SQL Server 2012 SP3 企业版中为应用程序运行的特定查询创建计划指南,这意味着我无法以任何方式更改查询。

查询如下所示:

(@P1 nvarchar(5),@P2 bigint)
DELETE FROM INVENTSUMDELTA WHERE ((DATAAREAID=@P1) AND (TTSID=@P2))

为了创建计划指南,我使用了以下查询:

EXEC sp_create_plan_guide   
@name = N'INVENTSUMDELTAINDEX', 
@stmt = N'DELETE FROM INVENTSUMDELTA WHERE ((DATAAREAID=@P1) AND (TTSID=@P2))',    
@type = N'SQL',  
@module_or_batch = NULL,  
@params = N'@P1 nvarchar(5),@P2 bigint',  
@hints = N'OPTION (TABLE HINT ( INVENTSUMDELTA, INDEX( I_2397TTSDIMIDX )))';

但是,我收到一个错误:

消息 8724,第 16 级,状态 1,第 1 行 无法执行查询。不能在 TABLE HINT 子句中指定表值或 OPENROWSET 函数“INVENTSUMDELTA”。

我检查了文档,发现以下内容:

表格提示 (exposed_object_name [ , [ [, ]...n ] ] ) 将指定的表提示应用于与exposed_object_name 对应的表或视图。 [...]

exposed_object_name 可以是以下引用之一:

  • 当查询的 FROM 子句中的表或视图使用别名时,exposed_object_name 是别名。

  • 不使用别名时,exposed_object_name 与 FROM 子句中引用的表或视图完全匹配。例如,如果使用两部分名称引用表或视图,则exposed_object_name 是相同的两部分名称。

据此,我认为应该可以为不使用别名的查询创建计划指南。但是,我无法让它工作。

所以我的问题是:如何在不使用别名且不更改原始查询的情况下创建计划指南?

【问题讨论】:

  • 有一个表(在 dbo 模式中),但没有 UDF。

标签: sql-server sql-server-2012


【解决方案1】:

错误信息具有误导性。它与对象的性质无关(您将得到与不存在的对象相同的错误)。问题是它不适用于DELETE 语句——任何引用作为DELETE 目标的表的TABLE HINT 都会产生此错误。这也不限于计划指南——带有选项的普通 DELETE 也会失败:

DELETE FROM does_not_exist 
OPTION (TABLE HINT (does_not_exist, INDEX (does_not_exist)))

无法执行查询。不能在 TABLE HINT 子句中指定表值或 OPENROWSET 函数“does_not_exist”。

这似乎是一个错误,因为如果在表和查询级别都使用WITH (ROWLOCK) 提示来扩充语句,错误就会消失:

DELETE FROM does_not_exist WITH (ROWLOCK)
OPTION (TABLE HINT (does_not_exist, ROWLOCK, INDEX (does_not_exist)))

无效的对象名称“does_not_exist”。

this question 中涵盖了相同的问题,解决方案是以允许应用提示的形式重写查询。

在这种情况下,我们不能直接重写查询,但是使用fixed query plan guide仍然可以得到想要的效果:

-- Alternate query using hint.
DECLARE @sql NVARCHAR(MAX) = N'WITH T AS (
    SELECT * 
    FROM INVENTSUMDELTA WITH (INDEX (I_2397TTSDIMIDX))
    WHERE ((DATAAREAID=@P1) AND (TTSID=@P2))
)
DELETE T';
DECLARE @params NVARCHAR(MAX) = N'@P1 nvarchar(5),@P2 bigint'

-- Put the execution plan in the cache.
EXEC sp_executesql @sql, @params = @params, @P1=NULL, @P2=NULL;

-- Retrieve it.
DECLARE @query_plan NVARCHAR(MAX);
SELECT @query_plan = query_plan  
FROM sys.dm_exec_query_stats AS qs   
CROSS APPLY sys.dm_exec_sql_text(qs.[sql_handle]) AS st  
CROSS APPLY sys.dm_exec_text_query_plan(qs.[plan_handle], DEFAULT, DEFAULT) AS qp  
WHERE st.[text] LIKE '(' + @params + ')%' + @sql;

-- Create a plan guide associating the query with the new execution plan.
EXEC sp_create_plan_guide   
    @name = N'INVENTSUMDELTAINDEX', 
    @stmt = N'DELETE FROM INVENTSUMDELTA WHERE ((DATAAREAID=@P1) AND (TTSID=@P2))',    
    @type = N'SQL',  
    @module_or_batch = NULL,  
    @params = @params,
    @hints = @query_plan;

与往常一样,如果没有其他帮助(更新统计信息、创建新索引、删除次优索引),计划指南应该是最后的手段。此答案假定您已查看所有其他选项并且计划指南是必要的。

【讨论】:

  • 感谢您的回答!我认为这与 DELETE 缺少适当的 FROM 子句有关,但没有想到这种解决方法。我要到下周才能尝试解决方案,所以我会等到那时将其标记为已接受。
  • @Monzie:我验证了它确实有效(通过创建第二个索引并在打开和关闭计划指南的语句之间比较执行计划),所以如果它在您的情况下不起作用,请务必添加详细信息。特别是,使用计划指南匹配查询文本和参数化可能会很棘手。
  • @Monzie:还有一件事——我不确定执行计划是否反映了传入参数的行估计值。如果是这样,可能需要在执行时传入代表性参数值(在回滚以防止实际删除行的事务中!)或使用OPTIMIZE FOR 提示提供代表值。这是使用固定查询计划的负面结果,而不是仅使用提示的指南。请注意,OPTIMIZE FOR 本身可能会启用索引,从而使整个固定查询变得不必要。
  • 太棒了!这行得通。此查询来自需要大量索引提示的 Dynamics AX。感谢您提供出色的解决方法。毕竟,计划指南的重点是指示 SQL 如何在无法修改它们的情况下执行 3rd 方查询。
猜你喜欢
  • 1970-01-01
  • 2016-02-05
  • 2010-11-22
  • 1970-01-01
  • 2011-04-09
  • 2017-11-06
  • 2012-01-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多