【问题标题】:Stored procedure slow when called from web, fast from Management Studio从 Web 调用存储过程时速度慢,从 Management Studio 调用时速度快
【发布时间】:2011-09-28 22:47:15
【问题描述】:

我的存储过程每次从 Web 应用程序调用时都会疯狂超时。

我启动了 Sql Profiler 并跟踪了超时的调用,最后发现了这些事情:

  1. 当从 MS SQL Management Studio 中执行语句时,使用相同的参数(实际上,我从 sql 配置文件跟踪复制了过程调用并运行它):平均在 5~6 秒内完成。
  2. 但是当从 Web 应用程序调用时,它需要超过 30 秒(在跟踪中),所以我的网页到那时实际上已经超时。

除了我的 Web 应用程序有自己的用户这一事实之外,每件事都是相同的(相同的数据库、连接、服务器等) 我还尝试使用 Web 应用程序的用户直接在工作室中运行查询,并且不会超过 6 秒。

我如何知道发生了什么?

我假设这与我们使用 BLL > DAL 层或表适配器这一事实无关,因为跟踪清楚地表明延迟存在于实际过程中。我能想到的就这些了。

编辑我在 this link 中发现 ADO.NET 将 ARITHABORT 设置为 true - 这在大多数情况下都很好,但有时会发生这种情况,建议的解决方法是将with recompile 选项添加到存储过程。就我而言,它不起作用,但我怀疑它与此非常相似。任何人都知道 ADO.NET 还能做什么或我在哪里可以找到规范?

【问题讨论】:

  • 这可能与返回的数据量有关?
  • @Barry:不,因为我在管理工作室中运行相同的程序(也从跟踪复制 - 意味着相同的参数),它在 6 秒内运行。
  • @Jayantha:关键不是 sp 慢,而是 ado.net 和 sql 之间的一些东西。我看不出 sp 会有什么不同。
  • SP 是否返回大量数据,例如 image/text/varchar(max) 列?在客户端上消耗的数据量将是巨大的,这将花费大量时间。 SSMS 以更高效的方式切断这些结果集。

标签: asp.net sql-server-2008 stored-procedures


【解决方案1】:

我过去也遇到过类似的问题,所以我很想看到这个问题的解决方案。 Aaron Bertrand 对 OP 的评论导致Query times out when executed from web, but super-fast when executed from SSMS,虽然问题不是重复的,但答案很可能适用于您的情况。

从本质上讲,SQL Server 可能有一个损坏的缓存执行计划。您的 Web 服务器遇到了错误的计划,但 SSMS 采用了不同的计划,因为 ARITHABORT 标志上有不同的设置(否则对您的特定查询/存储过程没有影响)。

另一个例子见ADO.NET calling T-SQL Stored Procedure causes a SqlTimeoutException,有更完整的解释和解决方案。

【讨论】:

  • 这些是一些有趣的线索,将阅读更多关于它们的信息,并在稍后回复......谢谢。
  • 嗨,清除缓存 DBCC DROPCLEANBUFFERSDBCC FREEPROCCACHE 成功了!我猜执行计划以某种方式损坏或未更新。我怀疑这是一个永久性的修复,如果它可能会损坏一次,它可以再做一次。所以我仍在寻找可能的原因。由于网络应用程序再次恢复工作,我不需要感到压力。非常感谢。
  • @iamserious - 你成功了,谢谢!更改我的存储过程后,我遇到了超时问题。然后我运行了你上面提到的两个 DBCC 命令,它解决了这个问题。
  • @Mangist:因为这是一个复杂的问题,所以没有灵丹妙药,但您可以尝试在导致问题的查询上使用OPTIMIZE FOROPTION(Recompile) 查询提示。 This article 深入讨论这个问题。如果 Entity Framework 正在生成您的查询,this post 似乎提供了一种向您的查询添加查询提示的方法。
  • 您可以删除并重新创建有问题的存储过程,而不是运行 DBCC DROPCLEANBUFFERS 和 DBCC FREEPROCCACHE 来清除所有执行计划。
【解决方案2】:

我还发现查询在网络上运行缓慢而在 SSMS 中运行得很快,我最终发现问题出在参数嗅探上。

我的解决方法是将存储过程中使用的所有参数更改为局部变量。

例如。改变:

ALTER PROCEDURE [dbo].[sproc] 
    @param1 int,
AS
SELECT * FROM Table WHERE ID = @param1 

到:

ALTER PROCEDURE [dbo].[sproc] 
    @param1 int,
AS
DECLARE @param1a int
SET @param1a = @param1
SELECT * FROM Table WHERE ID = @param1a

看起来很奇怪,但它解决了我的问题。

【讨论】:

  • 哇,我遇到了同样的问题,不相信这会奏效,但确实奏效了。
  • Zane,你知道这是否会永久解决问题还是会再次出现?谢谢!
  • 自从进行此更改后,我对存储过程的速度没有任何问题。
  • 会不会是修改 proc 会导致创建新的执行计划,所以解决方案并不是真正将输入变量复制到局部变量,而只是强制生成新的执行计划(如在接受的解决方案中解释)?
  • 是的,这真的不是答案。您只需重新编译存储过程即可获得相同的更改。您至少应该运行一周并确保它确实已修复。
【解决方案3】:

不是垃圾邮件,但作为对其他人有帮助的解决方案,我们的系统出现了高度超时。

我尝试使用 sp_recompile 将存储过程设置为重新编译,这解决了一个 SP 的问题。

最终有更多的 SP 超时,其中许多以前从未这样做过,通过使用 DBCC DROPCLEANBUFFERSDBCC FREEPROCCACHE,超时的发生率显着下降 - 仍然存在孤立的事件,有些地方我怀疑计划的更新需要一段时间,而有些地方的 SP 确实表现不佳,需要重新评估。

【讨论】:

  • 谢谢。当DBCC DROPCLEANBUFFERSDBCC FREEPROCCACHE 没有区别时,使用 sp_recompile 命令标记重新编译过程对我有用。
【解决方案4】:

会不会是在 Web 应用程序调用 SP 之前进行的其他一些 DB 调用使事务保持打开状态?这可能是该 SP 在被 Web 应用程序调用时等待的原因。我说隔离 Web 应用程序中的调用(将其放在新页面上)以确保 Web 应用程序中的某些先前操作导致此问题。

【讨论】:

  • 嗨@Tundey,我隔离了呼叫,它仍然需要大约 30 秒并超时。所以它必须是通信之间的东西,我猜?
【解决方案5】:

只需重新编译存储过程(在我的例子中是表函数)对我有用

【讨论】:

    【解决方案6】:

    就像@Zane 所说的,这可能是由于参数嗅探。我经历了同样的行为,我查看了过程的执行计划和 sp 的所有语句连续(复制了过程中的所有语句,将参数声明为变量并为变量分配了相同的值参数有)。然而,执行计划看起来完全不同。 sp 执行耗时 3-4 秒,并且立即返回具有完全相同值的行中的语句。

    经过一番谷歌搜索后,我发现了一篇关于这种行为的有趣文章:Slow in the Application, Fast in SSMS?

    在编译过程时,SQL Server 不知道@fromdate 的值发生了变化,而是在@fromdate 的值为NULL 的假设下编译过程。由于所有与 NULL 的比较都会产生 UNKNOWN,因此如果 @fromdate 在运行时仍然具有此值,则查询根本无法返回任何行。如果 SQL Server 将输入值作为最终事实,它可以构造一个只有一个常量扫描的计划,它根本不访问表(运行查询 SELECT * FROM Orders WHERE OrderDate > NULL 以查看此示例) .但是无论@fromdate 在运行时有什么值,SQL Server 都必须生成一个返回正确结果的计划。另一方面,没有义务制定一个最适合所有价值观的计划。因此,由于假设不会返回任何行,SQL Server 选择了 Index Seek。

    问题是我的参数可以保留为 null,如果它们作为 null 传递,则会使用默认值进行初始化。

    create procedure dbo.procedure
        @dateTo datetime = null
    begin
        if (@dateTo is null)
        begin
            select @dateTo  = GETUTCDATE()
        end
    
        select foo
        from dbo.table
        where createdDate < @dateTo
    end
    

    我改成之后

    create procedure dbo.procedure
        @dateTo datetime = null
    begin
        declare @to datetime = coalesce(@dateTo, getutcdate())
    
        select foo
        from dbo.table
        where createdDate < @to
    end 
    

    它再次像魅力一样发挥作用。

    【讨论】:

      【解决方案7】:
      --BEFORE
      CREATE PROCEDURE [dbo].[SP_DEMO]
      ( 
          @ToUserId bigint=null
       )
      AS
      BEGIN
      SELECT * FROM tbl_Logins WHERE LoginId = @ToUserId 
      END
      --AFTER CHANGING TO IT WORKING FINE
      CREATE PROCEDURE [dbo].[SP_DEMO]
      ( 
          @ToUserId bigint=null
       )
      AS
      BEGIN
      DECLARE @Toid bigint=null
      SET @Toid=@ToUserId
      SELECT * FROM tbl_Logins WHERE LoginId = @Toid 
      END
      

      【讨论】:

        【解决方案8】:

        您可以通过以下方式定位特定的缓存执行计划:

        SELECT cp.plan_handle, st.[text]
        FROM sys.dm_exec_cached_plans AS cp 
        CROSS APPLY sys.dm_exec_sql_text(plan_handle) AS st
        WHERE [text] LIKE N'%your troublesome SP or function name etc%'
        

        然后仅删除导致问题的执行计划,例如:

        DBCC FREEPROCCACHE (0x050006003FCA862F40A19A93010000000000000000000000)
        

        我现在有一个工作每 5 分钟运行一次,它会查找运行缓慢的过程或函数,并在发现时自动清除这些执行计划:

        if exists (
            SELECT cpu_time, *
            FROM sys.dm_exec_requests req
            CROSS APPLY sys.dm_exec_sql_text(sql_handle) AS sqltext
            --order by req.total_elapsed_time desc
            WHERE ([text] LIKE N'%your troublesome SP or function name etc%')
            and cpu_time > 8000
        )
        begin
        
            SELECT cp.plan_handle, st.[text]
            into #results
            FROM sys.dm_exec_cached_plans AS cp 
            CROSS APPLY sys.dm_exec_sql_text(plan_handle) AS st
            WHERE [text] LIKE N'%your troublesome SP or function name etc%'
            delete #results where text like 'SELECT cp.plan_handle%'
            --select * from #results
        
            declare @handle varbinary(max)
            declare @handleconverted varchar(max)
            declare @sql varchar(1000)
        
            DECLARE db_cursor CURSOR FOR  
            select plan_handle from #results
        
            OPEN db_cursor   
            FETCH NEXT FROM db_cursor INTO @handle
        
            WHILE @@FETCH_STATUS = 0   
            BEGIN   
        
                --e.g. DBCC FREEPROCCACHE (0x050006003FCA862F40A19A93010000000000000000000000)
                print @handle
                set @handleconverted = '0x' + CAST('' AS XML).value('xs:hexBinary(sql:variable("@handle"))', 'VARCHAR(MAX)')
                print @handleconverted
                set @sql = 'DBCC FREEPROCCACHE (' + @handleconverted + ')'
                print 'DELETING: ' + @sql
                EXEC(@sql)
        
                FETCH NEXT FROM db_cursor INTO @handle
            END   
        
            CLOSE db_cursor   
            DEALLOCATE db_cursor
        
            drop table #results
        
        end
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2015-06-27
          • 1970-01-01
          • 2011-03-22
          • 2023-04-10
          • 2011-11-10
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多