什么是更新存储过程368次以更新数据库的好方法?

我正在研究一个.NET组件,它从数据库中获取一组数据,对该组数据执行一些业务逻辑,然后通过类似于spUpdateOrderDetailDiscountedItem的存储过程更新数据库中的单个记录。

对于小数据集,这不是问题,但是当我有一个非常大的数据集需要迭代368个存储过程调用来更新数据库中的记录时,我意识到我遇到了问题。 一位高级开发人员查看了我的存储过程代码并说它看起来不错,但现在我想探索一种更好的方法来将“批量”数据发送到数据库。

我有哪些选项可以批量更新数据库? 这可能与存储过程有关吗? 我还有其他选择吗?

我不会选择安装完整的ORM,但任何建议都表示赞赏。


其他背景资料:

我们当前的数据访问模型是在5年前构建的,目前对db的所有调用都是通过模块化/静态函数执行的,其名称类似于ExecQueryGetDataTable 。 我不确定我是否需要保持在该模型中,但是我必须提供一个非常好的理由来超越我们当前的DAL来获取数据库。

另外值得注意的是,在谈到CRUD操作和数据库时,我还是比较新的。 我更喜欢在.NET代码中玩/工作,但数据必须存储在某个地方,对吧?


存储过程内容:

 ALTER PROCEDURE [dbo].[spUpdateOrderDetailDiscountedItem] -- Add the parameters for the stored procedure here @OrderDetailID decimal = 0, @Discount money = 0, @ExtPrice money = 0, @LineDiscountTypeID int = 0, @OrdersID decimal = 0, @QuantityDiscounted money = 0, @UpdateOrderHeader int = 0, @PromoCode varchar(6) = '', @TotalDiscount money = 0 AS BEGIN -- SET NOCOUNT ON added to prevent extra result sets from -- interfering with SELECT statements. SET NOCOUNT ON; -- Insert statements for procedure here Update OrderDetail Set Discount = @Discount, ExtPrice = @ExtPrice, LineDiscountTypeID = @LineDiscountTypeID, LineDiscountPercent = @QuantityDiscounted From OrderDetail with (nolock) Where OrderDetailID = @OrderDetailID if @UpdateOrderHeader = -1 Begin --This code should get code the last time this query is executed, but only then. exec spUpdateOrdersHeaderForSkuGroupSourceCode @OrdersID, 7, 0, @PromoCode, @TotalDiscount End 

我在使用中看到的一种简单而另类的方法是构建一个SQL语句,该语句由sql_execs组成,并使用字符串中的参数调用sproc。 不确定是否建议这样做,但是从.NET的角度来看,你只填充一个SqlCommand并调用ExecuteNonQuery一次……

请注意,如果您选择此项,请使用StringBuilder ! 🙂

更新:我更喜欢Chris Lively的答案,直到现在才知道表值参数…不幸的是OP正在使用2005。

如果您使用的是SQL 2008,则可以使用表值参数在一个s’proc调用中推送所有更新。

更新顺便提一下,我们将它与merge语句结合使用。 这样,sql server负责确定我们是否插入新记录或更新现有记录。 此机制在我们的Web应用程序中的几个主要位置使用,并且一次处理数百个更改。 在常规加载期间,我们将看到这个proc每秒被调用大约50次,它比我们发现的任何其他方式快得多……当然比购买更大的数据库服务器便宜很多。

您可以将完整的数据集作为XML输入发送到存储过程。 然后,您可以执行Set操作来修改数据库。 基于集合几乎每次都会在性能上击败RBAR。

如果您使用的是2008之前的SQL Server版本,则可以将代码完全移动到存储过程本身。

对此有好的和“坏的”。

  • 无需通过网络线路提取数据。
  • 如果您的逻辑基于设置,则更快
  • 放大

  • 如果您有针对数据库中任何逻辑的规则,这将破坏您的设计。
  • 如果无法基于逻辑设置,那么最终可能会遇到一组不同的性能问题
  • 如果您有外部依赖关系,这可能会增加难度。

如果没有关于您正在对数据执行的操作的详细信息,则很难给出可靠的建议。

UPDATE
Ben在我关于CLR和SQL Server的一条评论中询问了我的意思。 在SQL Server 2005中使用CLR集成进行读取。 基本思想是你可以编写.Net代码来进行数据操作,并将代码存放在SQL服务器本身内部。 这使您无需通过网络读取所有数据并以此方式发送更新。

代码可以通过现有的proc来调用,并为您提供.net的全部function,这样您就不必执行游标等操作。 当.net代码可以对单个记录执行操作时,sql将保持基础设置。

顺便说一句,这就是在SQL 2008中实现heirarchyid之类的东西。

唯一真正的缺点是某些DBA不喜欢将这样的开发人员代码引入数据库服务器。 因此,根据您的环境,这可能不是一个选择。 但是,如果是,那么在将数据和处理留在数据库服务器中时,它是一种非常强大的方法来处理您的问题。

你能用368次调用你的proc来创建批量声明,那么至少你不会有368次往返。 即伪代码

 var lotsOfCommands = "spUpdateOrderDetailDiscountedItem 1; spUpdateOrderDetailDiscountedItem 2;spUpdateOrderDetailDiscountedItem ... 368' var new sqlcommand(lotsOfCommands) command.CommandType = CommandType.Text; //execute command 

尝试同样的事情时我遇到了问题(通过插入,更新等)。 使用带参数的OleDbCommand时,每次调用它时都需要花费大量时间不断重新创建对象和参数。 所以,我在我的对象上创建了一个属性来处理这样的调用,并且还为函数添加了适当的“参数”。 然后,当我需要实际调用/执行它时,我会遍历对象中的每个参数,将其设置为我需要的任何参数,然后执行它。 这创造了显着的性能提升……我的操作的伪代码:

 protected OleDbCommand oSQLInsert = new OleDbCommand(); // the "?" are place-holders for parameters... can be named parameters, // just for visual purposes oSQLInsert.CommandText = "insert into MyTable ( fld1, fld2, fld3 ) values ( ?, ?, ? )"; // Now, add the parameters OleDbParameter NewParm = new OleDbParameter("parmFld1", 0); oSQLInsert.Parameters.Add( NewParm ); NewParm = new OleDbParameter("parmFld2", "something" ); oSQLInsert.Parameters.Add( NewParm ); NewParm = new OleDbParameter("parmFld3", 0); oSQLInsert.Parameters.Add( NewParm ); 

现在,SQL命令和调用的占位符都已准备就绪……然后,当我准备好实现调用它时,我会做类似的事情……

 oSQLInsert.Parameters[0].Value = 123; oSQLInsert.Parameters[1].Value = "New Value"; oSQLInsert.Parameters[2].Value = 3; 

然后,执行它。 通过一遍又一遍地创建命令,可以通过时间来重复100次的呼叫…

祝好运。

这是一次性行动(例如“只是导入那些368个新客户”)还是经常要做368次拨打电话?

如果这是一次性动作,只需要拨打368个电话。
(如果sproc做的不仅仅是更新,而且可能会降低性能,可以在晚上或晚上或任何人工作时运行)。

IMO,过早优化一次性操作的数据库调用是不值得花费的时间。

批量CSV导入

(1)通过字符串生成器将数据输出构建为CSV然后执行批量CSV导入:

http://msdn.microsoft.com/en-us/library/ms188365.aspx

表值参数最好,但由于您使用的是SQL 05,因此可以使用SqlBulkCopy类插入批量记录。 根据我的经验,这是非常快的。