【问题标题】:T-SQL parameter sniffing recompile planT-SQL 参数嗅探重编译计划
【发布时间】:2014-12-01 14:44:43
【问题描述】:

我有 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 命令 option(recompile) 的末尾,它的工作速度很快:在 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 FREEPROCCACHE
  • DBCC UPDATEUSAGE (0)
  • DBCC FREESYSTEMCACHE ('ALL')
  • 更改索引并重建 不幸的是,所有这些操作都对我没有帮助。

【问题讨论】:

  • 你为什么要在动态sql中这样做?此外,前导通配符会导致您的查询不可搜索。
  • 为什么你不想使用OPTION (RECOMPILE)
  • Sean Lange:我有一个带有过滤器选项的网格。当用户在网格中键入 somevalue 时,我会生成带有前导通配符的 SQL 命令。所以我只是将 '*' 转换为 '%'
  • GarethD:我读到该选项(重新编译)会增加一些开销,因为它会在每次执行查询时重建执行计划..

标签: sql sql-server tsql


【解决方案1】:

你可以试试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 来定位该值。

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

  • 由变量提供的参数
  • 相关字段的值分布不均(因此通过变量传入的不同值可能会产生不同的计划)

在下面的 cmets 中确定了以下情况,并不都需要此提示,因此以下是解决每种情况的方法:

  • 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) 提示的好机会。

  • 如果存在这样一种情况,即传入的变量匹配行的分布变化很大,但传入的可能值不多,并且传入的值被频繁重用,那么那些可以直接连接到动态 SQL 中(在对其执行 REPLACE(@var, '''', '''''') 之后)。这允许这些值中的每一个都有自己独立但可重用的查询计划。其他变量应该像往常一样作为参数发送。

    例如,[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;

【讨论】:

  • 问题是我不想每次运行 SQL 命令时都计算执行计划,因为我有很多类似的查询,我想使用保存的计划来避免开销。但是你的回答让我想到了在 SQL 命令包含“Like”运算符的情况下调用选项(优化)!
  • @driver :此选项不会每次都重新计算计划。这就是它的想法,否则它将与 RECOMPILE 相同。它专为您需要的目的而设计。
  • srutzky:当我将选项(OPTIMIZE FOR UNKNOWN)添加到我的 SQL 命令并使用相同的参数和不同的参数值运行相同的 SQL 时会发生什么?第一次运行时会根据实际统计信息创建一个执行计划并保存在一些缓存中,第二次如果统计信息没有变化会使用相同的计划吗?
  • @driver 为什么要问我,如果在您的系统上测试它更快、更无争议,会发生什么?那会给你一个确定的答案。但可以肯定的是,它不会重做所有原始工作,因为这与 RECOMPILE 没有什么不同。
  • This article should help - 但要回答您的问题,当您使用优化未知时,查询不会重新编译,它将基于“平均”值创建一个计划,并使用该计划对于所有传递的值。这可以防止在离群值上创建计划,但它仍然意味着对某些值使用次优计划。这在很大程度上取决于您的数据,以及您运行查询的频率,以确定重新编译的成本是否超过了使用错误计划运行查询的成本。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-11-03
  • 2021-06-23
  • 2014-09-28
  • 2016-10-07
  • 2019-06-19
  • 2011-09-01
  • 2010-09-17
相关资源
最近更新 更多