我们有一个存储过程,可以构建一些动态SQL并通过对sp_executesql
的参数化调用来执行。
在正常情况下,这非常有效,并且在程序的执行时间(~8秒到~1秒)中获得了很大的好处,但是,在某些未知条件下,会发生一些奇怪的事情,并且性能完全在另一个方式(约31秒),但仅在通过RPC执行时(即来自。SqlCommand.CommandType
CommandType.StoredProcedure
{。}}的.Net应用程序;或作为来自链接服务器的远程查询) - 如果执行为使用SQL Server Management Studio的SQL Batch,我们看不到性能下降。
改变生成的SQL中的空格并重新编译存储过程似乎至少在短期内解决了这个问题,但是我们想了解原因,或者强制重建执行计划的方法用于生成的SQL;但此刻,我不确定如何处理?
为了说明,存储过程看起来有点像:
CREATE PROCEDURE [dbo].[usp_MyObject_Search]
@IsActive AS BIT = NULL,
@IsTemplate AS BIT = NULL
AS
DECLARE @WhereClause NVARCHAR(MAX) = ''
IF @IsActive IS NOT NULL
BEGIN
SET @WhereClause += ' AND (svc.IsActive = @xIsActive) '
END
IF @IsTemplate IS NOT NULL
BEGIN
SET @WhereClause += ' AND (svc.IsTemplate = @xIsTemplate) '
END
DECLARE @Sql NVARCHAR(MAX) = '
SELECT svc.[MyObjectId],
svc.[Name],
svc.[IsActive],
svc.[IsTemplate]
FROM dbo.MyObject svc WITH (NOLOCK)
WHERE 1=1 ' + @WhereClause + '
ORDER BY svc.[Name] Asc'
EXEC sp_executesql @Sql, N'@xIsActive BIT, @xIsTemplate BIT',
@xIsActive = @IsActive, @xIsTemplate = @IsTemplate
使用这种方法,查询计划将被缓存为NULL / not-NULL的排列,我们正在获得缓存查询计划的好处。我不明白为什么它会在“事情发生”后远程执行时使用不同的查询计划;我也不明白“某事”可能是什么?
我意识到我可以摆脱参数化,但是我们失去了缓存正常良好执行计划的好处。
答案 0 :(得分:3)
我怀疑参数嗅探。如果您使用的是SQL Server 2008,则可以尝试使用OPTIMIZE FOR UNKNOWN
来最小化生成计划时为非典型参数值执行此操作的可能性。
RE:What I don't understand is why it would use a different query plan when executed remotely vs. locally after "something happens"
当您在SSMS中执行时,由于不同的SET
选项(例如SET ARITHABORT ON
),它将不会使用相同的错误计划,因此它将编译一个适用于您的参数值的新计划目前正在测试。
您可以使用
查看这些计划SELECT usecounts, cacheobjtype, objtype, text, query_plan, value as set_options
FROM sys.dm_exec_cached_plans
CROSS APPLY sys.dm_exec_sql_text(plan_handle)
CROSS APPLY sys.dm_exec_query_plan(plan_handle)
cross APPLY sys.dm_exec_plan_attributes(plan_handle) AS epa
where text like '%FROM dbo.MyObject svc WITH (NOLOCK)%'
and attribute='set_options'
修改
以下位是对badbod99的答案的回应
create proc #foo @mode bit, @date datetime
as
declare @Sql nvarchar(max)
if(@mode=1)
set @Sql = 'select top 0 * from sys.objects where create_date < @date /*44FC79BD-2AF5-4774-9674-04D6C3D4B228*/'
else
set @Sql = 'select top 0 * from sys.objects where modify_date < @date /*44FC79BD-2AF5-4774-9674-04D6C3D4B228*/'
EXEC sp_executesql @Sql, N'@date datetime',
@date = @date
go
declare @d datetime
set @d = getdate()
exec #foo 0,@d
exec #foo 1, @d
SELECT usecounts, cacheobjtype, objtype, text, query_plan, value as set_options
FROM sys.dm_exec_cached_plans
CROSS APPLY sys.dm_exec_sql_text(plan_handle)
CROSS APPLY sys.dm_exec_query_plan(plan_handle)
cross APPLY sys.dm_exec_plan_attributes(plan_handle) AS epa
where text like '%44FC79BD-2AF5-4774-9674-04D6C3D4B228%'
and attribute='set_options'
返回
答案 1 :(得分:1)
<强>重新编译强>
任何时候由于条件语句而SP的执行会有很大不同,从上一个请求缓存的执行计划可能不适合这个。
所有关于SQL何时编译SP的执行计划。关于Microsoft docs上的sp编译的关键部分是:
...重新启动SQL Server后第一次运行存储过程时会自动执行此优化。如果存储过程使用的基础表发生更改,也会发生此问题。但是,如果添加了一个新索引,存储过程可能会从中受益,则在SQL Server重新启动后下次运行存储过程时才会进行优化。在这种情况下,强制存储过程在下次执行时重新编译会很有用
SQL确实会从Microsoft docs
重新编译执行计划当有利于执行此操作时,SQL Server会自动重新编译存储过程和触发器。
...但是每次调用都不会这样做(除非使用WITH RECOMPILE),所以如果每次执行都可能导致不同的SQL,那么你可能会遇到至少一次调用的旧计划。
RECOMPILE查询提示
The RECOMPILE query hint在检查语句级别需要重新编译的内容时会考虑您的参数值。
WITH RECOMPILE选项
WITH RECOMPILE(参见F部分)将导致执行计划与每次调用一起编译,因此您永远不会有一个次优计划,但您将获得编译开销。
重组为多个SP
查看您的具体情况,proc的执行计划永远不会更改,并且2个sql语句应该已经准备好执行计划。
我建议重组代码来拆分SP,而不是让这个有条件的SQL生成简化,确保你总是拥有最佳的执行计划而不需要任何SQL魔法。