admin

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

sql

我有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命令 选项(重新编译)的
末尾,则它的工作速度很快:Managements studio中显示了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免费程序
  • DBCC更新用法(0)
  • DBCC FREESYSTEMCACHE(’ALL’)
  • 不幸的是,ALTER INDEX和REBUILD的所有这些操作并没有帮助我。

阅读 248

收藏
2021-06-07

共1个答案

admin

您可以尝试使用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';

查询提示的MSDN页面指出“优化未知”:

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

此提示指示优化器使用 指定表总行数 除以 指定列的不同值数量
(即每个值的平均行数)作为行估计,而不是使用任何特定值的统计信息。正如@GarethD在以下评论中指出的那样:由于这可能会使某些查询受益,而可能伤害其他查询,因此需要对其进行测试,以查看由此带来的总收益是否是节省的净成本,而不是执行RECOMPILE的成本。有关更多详细信息,请查看:如何优化未知的工作原理

只是说明一下,取决于数据的分布和传入的值,如果使用的特定值的分布相当能代表大多数可以传入的值(即使与某些永远不会传入的值完全不同),那么您可以使用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
    这里传递了一个变量,并且以这样的方式使用该变量:对于不同的值,可能不会显示匹配行的一致数目,特别是由于前导导致无法使用索引%OPTION (OPTIMIZE FOR UNKNOWN)如上所述,这是获得提示的好机会。

  • 如果存在这样一种情况,即传入的变量的匹配行分布有很大不同,但是没有太多可能要传入的值,并且经常重复使用传入的值,则可以将它们串联起来(完成后REPLACE(@var, '''', ''''''))直接进入动态SQL。这允许这些值中的每一个都有自己单独的但可重复使用的查询计划。其他变量应像往常一样作为参数发送。

例如,[StatusID]的查找值将只有几个可能的值,并且它们将被频繁重用,但是每个特定的值都可以匹配行数截然不同的行。在这种情况下,如下所示将允许不需要RECOMPILE或OPTIMIZE
FOR UNKNOWN提示的单独执行计划,因为每个执行计划都将针对该特定值进行优化:

    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;

假设传入了两个不同的@StatusID值(例如12),则将缓存两个与以下查询匹配的执行计划:

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

* `SELECT TOP (10) * FROM myTableView WHERE [StatusID] = 2 AND [OtherField] = @OtherFieldVal;`
2021-06-07