【问题标题】:Call stored procedure with long running time调用运行时间长的存储过程
【发布时间】:2012-06-08 23:53:36
【问题描述】:

我正在尝试使用 C# 调用存储过程。存储过程的运行时间很长(3 到 4 分钟),当我尝试运行它们时它们会导致超时异常。我怎样才能让他们在更长的超时时间内完成运行?

而当它抛出异常时,存储过程是继续在服务器上运行,还是停止?

我的应用程序是一个游戏服务器,存储过程运行以更新和删除邮件。 它运行缓慢,因为有太多记录和太多连接,用户无法在 SP 运行的同时插入另一条记录。

我只需要运行SP,等待服务器响应较长时间。

@minitech 感谢您编辑我的问题

这是SP代码

ALTER    PROCEDURE [dbo].[SP_Mail_Scan]
 @NoticeUserID nvarchar(4000) output

AS  
Set @NoticeUserID=''
Declare @AuctionID Int        
Declare @AuctioneerID Int
Declare @AuctioneerName Nvarchar(100)
Declare @BuyerID Int
Declare @BuyerName Nvarchar(100)
Declare @ItemID Int
Declare @PayType Int
Declare @Price Int
Declare @Name Nvarchar(200)


Declare @MailID Int
Declare @SenderID Int
Declare @Sender Nvarchar(100)
Declare @ReceiverID Int
Declare @Receiver Nvarchar(100)
Declare @Title Nvarchar(1000)
Declare @Content Nvarchar(4000)
Declare @SendTime DateTime
Declare @IsRead Bit
Declare @IsDelR Bit
Declare @IfDelS Bit
Declare @IsDelete Bit
Declare @Annex1 Nvarchar(100)
Declare @Annex2 Nvarchar(100)
Declare @Gold Int
Declare @Money Int
Declare @Remark Nvarchar(200)
Declare @Annex3 Nvarchar(100)
Declare @Annex4 Nvarchar(100)
Declare @Annex5 Nvarchar(100)


Set @SenderID =0
Set @Sender =dbo.GetTranslation('SP_Mail_Scan.Sender')  --
Set @ReceiverID = ''
Set @Receiver = ''
Set @Title =dbo.GetTranslation('SP_Mail_Scan.Title')
Set @Content =dbo.GetTranslation('SP_Mail_Scan.Content')
Set @SendTime  = getdate()
Set @IsRead  = 0
Set @IsDelR = 0
Set @IfDelS = 0
Set @IsDelete =0
Set @Annex1 =''
Set @Annex2 =''
Set @Gold =0
Set @Money =0
Set @Annex3 =''
Set @Annex4 =''
Set @Annex5 =''

If object_id('tempdb..#PayMail') Is Not null
Drop Table #PayMail 

Create Table #PayMail 
(
id Int Identity(1,1), 
MailID Int Not Null,
SenderID Int Not Null,
Sender Nvarchar(200) Not null,
ReceiverID Int not null,
Receiver Nvarchar(200) not null,
Title Nvarchar(1000) not null,
Annex1 Nvarchar(100) not null,
Annex2 Nvarchar(100) not null,
Annex3 Nvarchar(100) not null,
Annex4 Nvarchar(100) not null,
Annex5 Nvarchar(100) not null,
) 

insert into #PayMail select [ID],SenderID,Sender,ReceiverID,Receiver,Title,isnull(Annex1,''),isnull(Annex2,''),isnull(Annex3,''),isnull(Annex4,''),isnull(Annex5,'') from User_Messages with(nolock) 
where IsExist=1 and Type>100 and datediff(hh,SendTime,getdate())>ValidDate and [Money]>0

  declare @NewTitle nvarchar(200)
  declare @NewContent nvarchar(200)
  set @NewTitle = dbo.GetTranslation('SP_Mail_Scan.Msg1')
  set @NewContent =dbo.GetTranslation('SP_Mail_Scan.Msg2')

set xact_abort on 
  begin tran

INSERT INTO User_Messages( SenderID, Sender, ReceiverID, Receiver, Title, Content, SendTime, IsRead, IsDelR, IfDelS, IsDelete, Annex1, Annex2, Gold, Money, IsExist,Type,Remark, Annex3, Annex4, Annex5) 
      select ReceiverID,Receiver,SenderID,Sender,@NewTitle+Title,REPLACE(@NewContent,'{0}',Receiver),getdate(), 0, 0, 0, 0,Annex1,Annex2,0,0,1,7,'Gold:0,Money:0,Annex1:'+Annex1+',Annex2:'+Annex2+',Annex3:'+Annex3+',Annex4:'+Annex4+',Annex5:'+Annex5,Annex3,Annex4,Annex5
    from #PayMail

  if @@error<>0 or @@ROWCOUNT =0
    begin
      rollback tran
      return 1 
   end

  update User_Messages set IsExist=0,@NoticeUserID = @NoticeUserID + cast(SenderID as nvarchar(50)) + ',' from User_Messages where [ID] in (select MailID from #PayMail)

  if @@error<>0 or @@ROWCOUNT =0
    begin
      rollback tran
      return 1 
   end

  commit tran
set xact_abort off

if len(@NoticeUserID)>0
begin
 set @NoticeUserID = substring(@NoticeUserID,1,len(@NoticeUserID)-1)
end
--set @NoticeUserID='100,200'

return 0

【问题讨论】:

标签: c# sql-server stored-procedures


【解决方案1】:

四分钟已经变得相当长了,如果以正确的方式设计和查询,即使是具有数百万行和大量表要连接的数据库也可以在几秒钟内返回结果。

您需要花一些时间处理存储过程并解释计划,以弄清楚为什么需要这么长时间。考虑数据分段、将查询拆分为离散块、索引、连接的构造方式、子选择等。Explain Plan 将告诉您它在哪里浪费时间。

存储过程不应花费四分钟的时间来运行,尤其是当它们在某种客户端应用程序上运行时。

我有一个遗留查询,它需要 52 分钟才能运行,经过一个小的重构,它是 40 秒,然后再过一点,6 秒。甚至 40 秒都在推动它。

如果您有如此庞大的查询并且它是基于报告的,请考虑使用数据仓库,因为它的架构设计更适合这些目的。

【讨论】:

  • +1:这是一个很好的答案 IMO。做一些好的研究并尝试分析瓶颈在哪里。不要简单地将超时时间设置为数光年。
  • 但问题不在于查询是否可以更快。也许OP甚至不允许更改SP。
  • @Andomar 可以通过更改表上的索引等来获得性能提升。不需要对 SP 进行更改。
【解决方案2】:

我想你正在使用 ADO.NET SqlCommand?

SqlCommand.CommandTimeout 会设置你需要的超时时间

http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlcommand.commandtimeout(v=vs.80).aspx

一旦客户端超时,存储过程就会停止。

【讨论】:

  • 如果 SQL Server 注意到客户端已断开连接,则存储过程将停止。它不会继续运行。
  • @Andomar 我从经验中得知存储过程会继续执行。我曾经也对这个问题感兴趣并对其进行了测试。你能告诉我一个客户端超时后服务器停止执行的设置吗?
  • 写一个永不结束的存储过程,比如while 1=1 begin insert YourTable values (1) end。让它从 C# 超时。
  • 是的,如果我记得的话,我就是这么做的。我用 sql server WAITFOR
  • @Andomar 实际上,服务器在超时后停止执行。正如您所说,我的发现可能因之前附加了调试器而受到影响。我更新了答案。谢谢你的收获
【解决方案3】:

您可以使用Asynchronous Procedure Execution。这是一种执行过程的可靠方式,客户端无需等待它完成。

但是像您描述的过程(必须定期运行并扫描邮件)更可能是一项工作,而SQL Agent 更适合。

【讨论】:

    【解决方案4】:

    根据您的示例代码,您对每个变量都使用了“声明”。为什么不对每个变量使用单个 Declare。像这样

    Declare  @AuctionID Int        
            ,@AuctioneerID Int
            ,@AuctioneerName Nvarchar(100)
            ,@BuyerID Int
            ,@BuyerName Nvarchar(100)
            ,@ItemID Int
            ,@PayType Int
            ,@Price Int
            ,@Name Nvarchar(200)
    
            ,@MailID Int
            ,@SenderID Int
            ,@Sender Nvarchar(100)
            ,@ReceiverID Int
            ,@Receiver Nvarchar(100)
            ,@Title Nvarchar(1000)
            ,@Content Nvarchar(4000)
            ,@SendTime DateTime
            ,@IsRead Bit
            ,@IsDelR Bit
            ,@IfDelS Bit
            ,@IsDelete Bit
            ,@Annex1 Nvarchar(100)
            ,@Annex2 Nvarchar(100)
            ,@Gold Int
            ,@Money Int
            ,@Remark Nvarchar(200)
            ,@Annex3 Nvarchar(100)
            ,@Annex4 Nvarchar(100)
            ,@Annex5 Nvarchar(100)
    

    为什么不为每个变量使用单个“选择”而不是“设置”

    Select   @SenderID =0
            ,@Sender = dbo.GetTranslation('SP_Mail_Scan.Sender')  --
            ,@ReceiverID = ''
            ,@Receiver = ''
            ,@Title = dbo.GetTranslation('SP_Mail_Scan.Title')
            ,@Content = dbo.GetTranslation('SP_Mail_Scan.Content')
            ,@SendTime  = getdate()
            ,@IsRead  = 0
            ,@IsDelR = 0
            ,@IfDelS = 0
            ,@IsDelete =0
            ,@Annex1 =''
            ,@Annex2 =''
            ,@Gold =0
            ,@Money =0
            ,@Annex3 =''
            ,@Annex4 =''
            ,@Annex5 =''
    

    您已经为插入操作创建了#table,但您没有为该表指定任何键,我建议您应该定义主键

    Create Table #PayMail 
    (
         id Int Identity(1,1) Primary Key
        ,MailID Int Not Null
        ,SenderID Int Not Null
        ,Sender Nvarchar(200) Not null
        ,ReceiverID Int not null
        ,Receiver Nvarchar(200) not null
        ,Title Nvarchar(1000) not null
        ,Annex1 Nvarchar(100) not null
        ,Annex2 Nvarchar(100) not null
        ,Annex3 Nvarchar(100) not null
        ,Annex4 Nvarchar(100) not null
        ,Annex5 Nvarchar(100) not null
    )
    

    并且您在插入#table 时使用了“with (Nolock)”,因此在从#table 中选择时也应该使用 with (Nolock)。如果您的#table 仅用于插入一次,那么我建议您应该使用 With CTE(通用表表达式)来相同,它可能会提高您的 SP 性能。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-09
      • 2018-04-23
      • 1970-01-01
      相关资源
      最近更新 更多