假设“成本”以时间为单位(尽管不确定;-),那么至少,您应该可以通过执行以下操作来了解它:
DBCC FREEPROCCACHE WITH NO_INFOMSGS;
SET STATISTICS TIME ON;
EXEC sp_help 'sys.databases'; -- replace with your proc
SET STATISTICS TIME OFF;
在“消息”选项卡中报告的第一项应为:
SQL Server解析和编译时间:
我将至少运行10次,并平均“ CPU”和“经过”毫秒。
理想情况下,您将在Production中运行此程序,以便获得真实的时间估算,但是很少有人允许人们在Production中清除计划缓存。幸运的是,从SQL Server 2008开始,可以从缓存中清除特定计划。在这种情况下,您可以执行以下操作:
DECLARE @SQL NVARCHAR(MAX) = '';
;WITH cte AS
(
SELECT DISTINCT stat.plan_handle
FROM sys.dm_exec_query_stats stat
CROSS APPLY sys.dm_exec_text_query_plan(stat.plan_handle, 0, -1) qplan
WHERE qplan.query_plan LIKE N'%sp[_]help%' -- replace "sp[_]help" with proc name
)
SELECT @SQL += N'DBCC FREEPROCCACHE ('
+ CONVERT(NVARCHAR(130), cte.plan_handle, 1)
+ N');'
+ NCHAR(13) + NCHAR(10)
FROM cte;
PRINT @SQL;
EXEC (@SQL);
SET STATISTICS TIME ON;
EXEC sp_help 'sys.databases' -- replace with your proc
SET STATISTICS TIME OFF;
但是,根据传递给导致“不良”缓存计划的参数的值的可变性,可以考虑另一种方法,该方法介于OPTION(RECOMPILE)
和之间OPTION(OPTIMIZE FOR UNKNOWN)
:动态SQL。是的,我说了。我什至指的是非参数化动态SQL。这就是为什么。
显然,您至少在一个或多个输入参数值方面具有不均匀分布的数据。上述选项的缺点是:
OPTION(RECOMPILE)
会为每次执行生成一个计划,即使再次传入的参数值与之前的运行相同,您也将永远无法从任何计划重用中受益。对于频繁调用的proc(每隔几秒钟或更频繁地一次),这将使您摆脱偶尔的可怕状况,但仍然使您始终处在一个并非所有的情况下。
OPTION(OPTIMIZE FOR (@Param = value))
会根据该特定值生成计划,这可能会帮助解决多种情况,但仍然使您对当前问题持开放态度。
OPTION(OPTIMIZE FOR UNKNOWN)
将根据平均分配量生成计划,这将有助于某些查询,但会损害其他查询。这应该与使用局部变量的选项相同。
但是,如果正确完成,Dynamic SQL 将允许传递的各种值具有各自独立的理想查询计划(当然,尽可能多)。此处的主要成本是,随着传入的值的多样性增加,缓存中的执行计划的数量也会增加,并且它们确实会占用内存。较小的费用是:
因此,当我每秒处理多个过程并击中多个表(每个表具有数百万行)时,这就是我如何处理这种情况的方法。我已经尝试过了,OPTION(RECOMPILE)
但是事实证明,在没有参数嗅探/错误的缓存计划问题的99%的情况下,这对过程太有害了。并且请记住,这些proc中的一个包含大约15个查询,并且其中只有3-5个被转换为Dynamic SQL,如此处所述;除非对特定查询必要,否则不使用动态SQL。
如果存储过程有多个输入参数,请找出哪些参数与数据分布高度不同的列一起使用(从而导致此问题),哪些参数与分布更均匀的列一起使用(并且不应导致此问题)。
使用与均匀分布的列关联的proc输入参数的参数构建动态SQL字符串。此参数化有助于减少与该查询相关的缓存中执行计划的结果增加。
对于与高度变化的分布关联的其余参数,应将它们作为文字值连接到Dynamic SQL中。由于唯一查询是由查询文本的任何更改决定的WHERE StatusID = 1
,因此,与具有相比,具有不同的查询,因此具有不同的查询计划WHERE StatusID = 2
。
如果要连接到查询文本中的任何proc输入参数是字符串,则需要对其进行验证以防止SQL注入(尽管如果传递的字符串是由SQL生成的,则这种情况不太可能发生)应用,而不是用户,但仍然)。至少这样做REPLACE(@Param, '''', '''''')
以确保单引号变为转义的单引号。
如果需要,请创建一个将用于创建用户的证书,并对存储过程进行签名,以便将直接表权限仅授予新的基于证书的用户,而不授予[public]
或授予不应该具有此类权限的用户。
示例过程:
CREATE PROCEDURE MySchema.MyProc
(
@Param1 INT,
@Param2 DATETIME,
@Param3 NVARCHAR(50)
)
AS
SET NOCOUNT ON;
DECLARE @SQL NVARCHAR(MAX);
SET @SQL = N'
SELECT tab.Field1, tab.Field2, ...
FROM MySchema.SomeTable tab
WHERE tab.Field3 = @P1
AND tab.Field8 >= CONVERT(DATETIME, ''' +
CONVERT(NVARCHAR(50), @Param2, 121) +
N''')
AND tab.Field2 LIKE N''' +
REPLACE(@Param3, N'''', N'''''') +
N'%'';';
EXEC sp_executesql
@SQL,
N'@P1 INT',
@P1 = @Param1;