【问题标题】:Is there a more efficient alternative to executing a Stored Procedure in a loop?在循环中执行存储过程是否有更有效的替代方法?
【发布时间】:2019-07-29 17:50:52
【问题描述】:

我在我的 asp.net 站点的网络表单中调用了大量存储过程,并在循环中执行。在大多数情况下,这很好,除非我使用更高的数量。事情变得非常缓慢。有没有其他方法可以让我的网站更高效?

例如,我有一个存储过程spReservePart,它执行以下操作:

    -- Add the parameters for the stored procedure here
    @startdate datetime, @enddate datetime, @SparePartID int, @notes nvarchar(MAX), @sr int, @assignto nvarchar(MAX), @newloc nvarchar(MAX)
AS
BEGIN
    -- SET NOCOUNT ON added to prevent extra result sets from
    -- interfering with SELECT statements.
    SET NOCOUNT ON;



    if (@sr = 0) begin set @sr = null end

    Insert into Reservations (ReservationID, StartDate, EndDate, SparePartID, Notes, Status, SRNumber, AssignedTo)
    Values (newid(), @startdate, @enddate, @SparePartID, @notes, 'Active', @sr, @assignto)

    Update MiddleMan Set ReservationID = (Select Top (1) ReservationID from Reservations where SparePartID=@SparePartID and Status = 'Active') where SparePartID=@SparePartID
    Update MiddleMan Set In_Use = 1 where SparePartID = @SparePartID
    Update MiddleMan Set SRHistory = (Select Top (1) SRNumber from Reservations where SparePartID=@SparePartID and Status = 'Active') where SparePartID = @SparePartID
    Update MiddleMan Set CurrentLocation = @newloc where SparePartID = @SparePartID

END

它来自的网络表单有一个带有复选框的 Gridview,对于每个选中的复选框,它都会调用这个存储过程。

foreach (GridViewRow item in gvreserveparts.Rows)
                {//for each row in the gridview
                    var chk = (CheckBox)item.FindControl("cbSelect");
                    if (chk.Checked == true) //checkbox is checked
                    {
                        //assignment of parameters here


                        //create connection with database
                        OleDbConnection conn = new OleDbConnection(connectionInfo);

                        OleDbCommand cmd = new OleDbCommand();
                        conn.Open();
                        cmd.Connection = conn;
                        cmd.CommandText = "spReservePart";
                        cmd.CommandType = CommandType.StoredProcedure;
                        cmd.Parameters.AddWithValue("@startdate", start);
                        cmd.Parameters.AddWithValue("@enddate", end);
                        cmd.Parameters.AddWithValue("@SparePartID", sparepartid);
                        cmd.Parameters.AddWithValue("@notes", notes);
                        cmd.Parameters.AddWithValue("@sr", sr);
                        cmd.Parameters.AddWithValue("@assignto", assignto);
                        cmd.Parameters.AddWithValue("@newloc", newloc);

                        cmd.ExecuteNonQuery();
                        conn.Close();
                    }


                }
            }

即使调用此 SP 的 6 次,也可能需要很长时间。因此,更有效的替代方案将非常有帮助。

【问题讨论】:

  • 您可以使用表值参数,然后重新设计您的过程以设置为基础。
  • 相关 - 不要使用addwithvalue
  • 根据我的经验,最快的方法是使用SqlBulkCopy 插入临时表,然后调用存储过程来处理插入和更新作为集合操作。我发现这比使用表值参数或 XML(这是另一种选择)快很多倍。作为一个无关的点,你为什么使用OleDbConnection而不是SqlConnection来连接SQL Server?
  • @SMor 虽然我完全同意使用强类型参数更可取,但性能问题在这里并不是什么大问题,因为它们使用的是存储过程。因此,数据类型解释不会发生,缓存也不是问题,因为它是被缓存的过程。
  • @SMor @Sean Lange 虽然我同意你们俩的观点,但我从未根据经验测试过添加强类型参数是否比AddWithValue 更快。我一直认为是的。你们中的任何一个人都检查过吗?

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


【解决方案1】:

您可以重写存储过程以接受表值参数,而不是将一组标量值参数传递给过程,然后您可以将单个表中的所有参数集传递给过程并且只执行一次。

【讨论】:

    【解决方案2】:

    获取表值参数是一个不错的选择,但您可以从更改过程开始以避免多次读取 Reservations 表并更新 MiddleMan 表 4 次。您可以使用 OUTPUT 子句消除读取并使用单个 UPDATE。

    CREATE PROCEDURE spReservePart 
        -- Add the parameters for the stored procedure here
        @startdate datetime, @enddate datetime, @SparePartID int, @notes nvarchar(MAX), @sr int, @assignto nvarchar(MAX), @newloc nvarchar(MAX)
    AS
    BEGIN
        -- SET NOCOUNT ON added to prevent extra result sets from
        -- interfering with SELECT statements.
        SET NOCOUNT ON;
    
        DECLARE @Output TABLE(
            ReservationId UNIQUEIDENTIFIER,
            SRNumber      INT,
            SparePartID   INT
        );
    
        IF (@sr = 0) 
            SET @sr = NULL;
    
        INSERT INTO Reservations (
            ReservationID, 
            StartDate, 
            EndDate, 
            SparePartID, 
            Notes, 
            Status, 
            SRNumber, 
            AssignedTo)
        OUTPUT  inserted.ReservationId, 
                inserted.SRNumber, 
                inserted.SparePartID 
        INTO @Output(
                ReservationId, 
                SRNumber, 
                SparePartID)
        VALUES (
            NEWID(), 
            @startdate, 
            @enddate, 
            @SparePartID, 
            @notes, 
            'Active', 
            @sr, 
            @assignto);
    
        UPDATE MiddleMan SET
            ReservationID   = o.ReservationID,
            In_Use          = 1,
            SRHistory       = o.SRNumber,
            CurrentLocation = @newloc
        FROM MiddleMan m
        JOIN @Output o ON m.SparePartID = o.SparePartID;
    END
    

    【讨论】:

    • 这是真的吗。这很可能是 OP 的意图,但没有 ORDER BYSELECT TOP (1) 是未定义的。根据我在这种情况下的经验,SQL Server 按聚集索引顺序提供结果,尽管不能保证这一点。我只是从未见过反例。
    【解决方案3】:

    你可以做的是改变你的存储过程来接受所有的数据作为一个表。

    将您的数据作为 dataTable 传递给存储过程。所以你必须调用一次存储过程。

    在这个链接中,有一个例子:

    How to send bulk data using table value parameters from c#

    这就是你应该如何改变你的存储过程来接受表:

    CREATE PROCEDURE [dbo].[SampleProcedure]
        (
         -- which accepts one table value parameter. 
         -- It should be noted that the parameter is read-only
         @Sample AS [dbo].[SampleDataType] READONLY
    )
    AS
    BEGIN
        INSERT INTO Reservations (ReservationID, StartDate, EndDate, SparePartID, 
                                  Notes, Status, SRNumber, AssignedTo)
            SELECT 
                ReservationID, StartDate,EndDate, ... 
            FROM
                @Sample
    END
    

    您可以在您的 C# 代码中创建一个 DataTable 并将其作为参数发送到存储过程

    var dataTable = new DataTable("SampleDataType"); 
    //create column names as per the type in DB
    dataTable.Columns.Add("startdate", typeof(DateTime)); 
    dataTable.Columns.Add("enddate", typeof(DateTime));
    dataTable.Columns.Add("SparePartID", typeof(Int32));  
    
    //...
    foreach (GridViewRow item in gvreserveparts.Rows)
    {
        var chk = (CheckBox)item.FindControl("cbSelect");
    
        if (chk.Checked == true) //checkbox is checked
        {
            dataTable.Rows.Add("startDate", start); 
            dataTable.Rows.Add("endDate", end); 
    //...
        }
    }
    
    var parameter = new SqlParameter(); 
    //The parameter for the SP must be of SqlDbType.Structured 
    parameter.ParameterName="@Sample"; 
    parameter.SqlDbType = System.Data.SqlDbType.Structured; 
    parameter.Value = dataTable; 
    
    var conn = new OleDbConnection(connectionInfo);
    var cmd = new OleDbCommand();
    cmd.Parameters.Add(parameter); 
    
    conn.Open();
    
    cmd.Connection = conn;
    cmd.CommandType = CommandType.StoredProcedure;
    cmd.CommandText = "spReservePart";
    
    cmd.ExecuteNonQuery();
    
    conn.Close();
    

    【讨论】:

      猜你喜欢
      • 2020-03-08
      • 2021-02-28
      • 2020-05-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-02-06
      • 1970-01-01
      • 2016-12-03
      相关资源
      最近更新 更多