【问题标题】:Stored Procedure Much Slower in .NET SqlCommand than in SSMS.NET SqlCommand 中的存储过程比 SSMS 中慢得多
【发布时间】:2013-02-04 22:27:14
【问题描述】:

我有一个正在执行 INSERT 语句的存储过程,我们在执行时看到了明显的延迟。从我们的 C# .NET 应用程序运行它以连续插入 30 条记录时,总共需要大约 4 秒才能完成(仅计算运行 SqlCommand.ExecuteNonQuery() 方法所需的时间)。但是,从 SQL Server Management Studio 中以相同的次数调用完全相同的存储过程只需要大约 0.4 秒。我不知道这 2 种设置之间有什么不同会产生如此大的 10 倍速度差异。

我已经尝试了以下所有方法,但速度没有明显变化:

  1. 创建存储过程“WITH RECOMPILE”
  2. 检查在 SSMS 和 C# 中配置的所有“SET”值。唯一的区别是 SET ARITHABORT,它在 SSMS 中为 ON,而在从 .NET 应用程序调用时为 OFF。不过,在存储过程的开头添加“SET ARITHABORT ON”并没有什么不同。
  3. 从 sproc 参数中删除了所有默认值

用于从 .NET 应用程序调用存储过程的代码是:

using (SqlConnection newConn = new SqlConnection(connectionString))
 {
   using (SqlCommand uCmd = new SqlCommand("sproc_name", newConn))
   {
      uCmd.CommandType = CommandType.StoredProcedure;
      uCmd.Connection.Open();

      //About 15 parameters added using:
      uCmd.Parameters.AddWithValue("@ParamName", value);
      ...

      //One output parameter
      SqlParameter paramOUT = new SqlParameter("@OutPutKey", SqlDbType.UniqueIdentifier);
      paramOUT.Direction = ParameterDirection.Output;
      uCmd.Parameters.Add(paramOUT);

      uCmd.ExecuteNonQuery();
      uCmd.Connection.Close();
   }
}

存储过程本身只是一个设置命令列表(SET ANSI_NULLS ON、SET QUOTED_IDENTIFIER ON、SET ARITHABORT ON)、一个非默认参数列表,以及将成为新唯一标识符的输出变量的设置将作为主键插入到表中,然后是 INSERT 语句本身。

应用程序基于 .NET 4 构建,SQL 服务器为 MS SQL Server 2005。

这是它调用的插入存储过程的示例:

alter procedure InsertStuff
@Field1 uniqueidentifier,
@Field2 datetime,
...
@CreateDate datetime,
@PrimaryKEY uniqueidentifier OUTPUT
AS

declare @newCreateDate datetime 
set @newCreateDate=getDate()

set @PrimaryKEY = NEWID()

INSERT INTO [dbo].[Table]
(
    Field1,
    Field2,
    ...
    CreateDate,
    PrimaryKEY
)
VALUES
(
    @Field1,
    @Field2,
    ...
    @newCreateDate,
    @PrimaryKEY
) 

【问题讨论】:

  • 没有看到存储过程很难回答,听起来你正在调用这个方法 30 次,但是从你的代码中你正在打开和关闭,将连接处理 30 次为出色地。保持连接打开,然后调用存储过程 30 次。
  • 也只设置一次命令,每次调用只需添加参数值
  • 这个问题可能对stackoverflow.com/questions/801909有帮助
  • 如果您可以访问 sql server 所在的机器,我会在该机器和您的机器上设置一些性能计数器(或任何数据访问代码所在的位置 - 如果是这样的话,就像 Web 服务器一样) )。如果您跟踪 Bytes Received 和 Bytes Sent,您应该能够弄清楚网络占用了多少时间,以及它真正从数据库中出来的速度有多快。
  • 如果此问题与 ARITH_ABORT 等环境问题有关,尝试在存储过程内部解决它们将无济于事;在调用 proc 之前,您需要在连接上设置 ARITHABORT ON - 并避免将您想要执行此操作的往返次数加倍,然后在同一连接上调用 proc 30 次而不“关闭”它(即使您正在使用连接池并且它并没有真正关闭)。

标签: c# .net sql ssms


【解决方案1】:

可能的问题是每个执行命令调用都会进行一次网络跳跃,因为 ssms 会一次将所有 30 个命令一次发送到服务器。我相信默认情况下 SSMS 会将所有 30 条语句作为一个批次发送,但如果您更改了其他可能会影响事情的设置。

另外,请确保您不是每次都打开和关闭连接。虽然连接池可能使这不是问题,但我不会放任不管。

【讨论】:

  • 连接池不会使其成为非问题,因为重用池连接仍然会导致额外的往返行程,因为连接被重置为同一连接上的新“会话”。 Wrt “ssms 将批量发送所有 30 个命令”,这真的取决于 SSMS 测试的完成方式(在单个批次中的循环中,或作为 30 个独立批次) - 将苹果与苹果进行比较是对的, SSMS 测试应实施为 30 个批次,用“GO”批次分隔符分隔。
  • @Tao 是的,可能会进行一些重置(我实际上听说池中的连接具有意外设置,例如不同的超时,所以我不确定实际重置了多少)。建立连接通常比发送命令更昂贵,但我明白你的意思。这实际上取决于完成了多少“重置”。我已经更新了我的答案,希望能更清楚地表明我假设 SSMS 的默认设置。
  • 原来我的测试环境搞砸了,实际上从.NET和SSMS调用这个插入存储过程的速度差不多。但是,我正在听取您关于共享单个 SqlConnection 和单个 SqlCommand 的建议(至少在我调用相同存储过程的情况下)。这让我的保存速度提高了大约 25%。不过,我仍然认为它应该能够比现在更快地完成这些插入。我添加了一个我的插入存储过程的示例。您认为还有其他改进的余地吗?
  • 原来聚集索引位于 uniqueidentifier 字段上,这显然是一个很大的禁忌。删除它可以显着提高性能。感谢您的所有建议,它们与创建日期字段上的新顺序索引相结合,将保存时间缩短了约 75%
  • @Rett,您应该添加您的评论作为答案并接受它,以便其他用户更容易看到它。
猜你喜欢
  • 1970-01-01
  • 2013-12-07
  • 2015-10-30
  • 1970-01-01
  • 2023-03-14
  • 1970-01-01
  • 1970-01-01
  • 2010-10-22
  • 2010-12-15
相关资源
最近更新 更多