T-SQL参数嗅探重新编译计划

时间:2014-12-01 14:44:44

标签: sql sql-server tsql

我有SQL命令

exec sp_executesql N'SELECT TOP (10) * FROM mytableView WHERE ([Name]) LIKE (''%'' + 
  (@Value0) + ''%'') ORDER BY [Id] DESC',N'@Value0 varchar(5)',@Value0='value'

这个sql命令在22秒后执行。我知道它发生了,因为我有一个参数嗅探.. 如果添加到SQL命令的结尾选项(重新编译)它可以快速工作:管理工作室中显示0秒

exec sp_executesql N'SELECT TOP (10) * FROM mytableView WHERE ([Name]) LIKE 
  (''%'' + (@Value0) + ''%'') ORDER BY [Id] DESC 
    option(recompile)',N'@Value0 varchar(5)',@Value0='value'

是否可以重新编译/重新创建/删除/更新我的SQL命令的执行计划,使其无需选项(重新编译)?

我试图申请

  • 更新统计数据
  • sp_recompile
  • DBCC FREEPROCCACHE
  • DBCC UPDATEUSAGE(0)
  • DBCC FREESYSTEMCACHE('ALL')
  • ALTER INDEX和REBUILD WITH 不幸的是,所有这些行动都没有帮助我。

1 个答案:

答案 0 :(得分:4)

您可以尝试OPTIMIZE FOR UNKNOWN提示而不是RECOMPILE

exec sp_executesql N'SELECT TOP (10) *
                     FROM mytableView
                     WHERE ([Name]) LIKE (''%'' + (@Value0) + ''%'')
                     ORDER BY [Id] DESC
                     option(OPTIMIZE FOR UNKNOWN);',
                   N'@Value0 varchar(5)',
                   @Value0 = 'value';

Query Hints的MSDN页面说明了OPTIMIZE FOR UNKNOWN:

  

指示查询优化器在编译和优化查询时使用统计数据而不是所有局部变量的初始值,包括使用强制参数化创建的参数。

此提示指示优化器使用指定表的总行数除以指定列的不同值的数量(即每个值的平均行数) )作为行估计而不是使用任何特定值的统计。正如@GarethD在下面的评论中所指出的那样:由于这可能会使一些查询受益并可能伤害其他查询,因此需要对其进行测试,以确定从中获得的总体收益是否比执行RECOMPILE的成本节省了净额。有关详细信息,请查看:How OPTIMIZE FOR UNKNOWN Works

并且只是说明了,根据数据的分布和传入的值,如果使用的是特定值,其分布可以很好地代表可以传入的大多数值(即使与某些不会传入的值有很大差异),您可以使用OPTIMIZE FOR (@Value0 = 'representative value')而不是OPTIMIZE FOR UNKNOWN来定位该值。

请注意,只有具有以下内容的查询才需要此查询提示:

  • 变量提供的参数
  • 所讨论的字段没有相当均匀的值分布(因此通过变量传递的不同值可能会生成不同的计划)

以下评论中确定了以下方案,并不是所有都需要此提示,因此以下是如何解决每种情况:

  • select top 80 * from table order by id desc
    这里没有传递变量,因此不需要查询提示。

  • select top 80 * from table where id < @lastid order by id desc
    这里传递了一个变量,但[id]字段本质上是均匀分布的,即使由于某些删除而稀疏,因此不需要查询提示(或者至少不需要查询提示)。

  • SELECT TOP (10) * FROM mytableView WHERE ([Name]) LIKE (''%'' + (@Value0) + ''%'') ORDER BY [Id] DESC
    这里传递了一个变量,并且以这样一种方式使用,即不能指示不同值的匹配行的一致数量,特别是由于无法使用索引作为前导{{1 }}。如上所述,这是%提示的好机会。

  • 如果传递的变量具有差异很大的匹配行分布,但传入的值不是很多,并且传入的值经常重复使用,那些可以直接连接到(在对它进行OPTION (OPTIMIZE FOR UNKNOWN)之后)到动态SQL中。这允许每个值都有自己独立但可重用的查询计划。其他变量应该像往常一样作为参数发送。

    例如,[StatusID]的查找值只有几个可能的值,它们会经常重复使用,但每个特定值可以匹配大量不同的行。在这种情况下,类似下面的内容将允许单独的执行计划,不需要RECOMPILE或OPTIMIZE FOR UNKNOWN提示,因为每个执行计划将针对该特定值进行优化:

    REPLACE(@var, '''', '''''')

    假设传递了两个不同的@StatusID值(例如IF (TRY_CONVERT(INT, @StatusID) IS NULL) BEGIN ;THROW 50505, '@StatusID was not a valid INT', 55; END; DECLARE @SQL NVARCHAR(MAX); SET @SQL = N'SELECT TOP (10) * FROM myTableView WHERE [StatusID] = ' + REPLACE(@StatusID, N'''', N'''''') -- really only needed for strings + N' AND [OtherField] = @OtherFieldVal;'; EXEC sp_executesql @SQL, N'@OtherFieldVal VARCHAR(50)', @OtherFieldVal = @OtherField; 1),则会有两个缓存的执行计划与以下查询匹配:

    • 2

    • SELECT TOP (10) * FROM myTableView WHERE [StatusID] = 1 AND [OtherField] = @OtherFieldVal;