您可以尝试使用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';
指示查询优化器在编译和优化查询时使用统计数据代替所有局部变量的初始值,包括使用强制参数化创建的参数。
此提示指示优化器使用 的 除以 的 (即每个值的平均行数)作为行估计,而不是使用任何特定值的统计信息。正如@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值(例如1
和2
),则将缓存两个与以下查询匹配的执行计划:
* `SELECT TOP (10) * FROM myTableView WHERE [StatusID] = 1 AND [OtherField] = @OtherFieldVal;`
* `SELECT TOP (10) * FROM myTableView WHERE [StatusID] = 2 AND [OtherField] = @OtherFieldVal;`