为什么在远程执行本地时存储过程的执行方式不同?

时间:2010-09-09 09:21:48

标签: sql-server performance

我们有一个存储过程,可以构建一些动态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的排列,我们正在获得缓存查询计划的好处。我不明白为什么它会在“事情发生”后远程执行时使用不同的查询计划;我也不明白“某事”可能是什么?

我意识到我可以摆脱参数化,但是我们失去了缓存正常良好执行计划的好处。

2 个答案:

答案 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'

返回

enter image description here

答案 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魔法。