如何进行批量更新?

6
我想知道是否有批量更新的方法?我正在使用ms sql server 2005。
我看到了sqlDataAdaptor,但似乎必须先用它选择语句,然后填充一些数据集并对数据集进行更改。
现在我正在使用linq to sql进行选择,所以我想尝试保持这种方式。但是,大规模更新太慢了。那么,有什么方法可以保留我的linq to sql(用于选择部分),但使用不同的东西进行大规模更新吗?
谢谢
编辑
我对这种暂存表的方式很感兴趣,但我不确定如何做以及如何更快,因为我不理解更新部分的工作原理。
那么,有人能向我展示这将如何运作以及如何处理并发连接吗?
编辑2
这是我最近尝试使用xml进行大规模更新的尝试,但它使用了太多资源,而我的共享主机不允许它通过。所以我需要一种不同的方式,因此我现在正在研究暂存表。
using (TestDataContext db = new TestDataContext())
            {
                UserTable[] testRecords = new UserTable[2];
                for (int count = 0; count < 2; count++)
                {
                    UserTable testRecord = new UserTable();

                    if (count == 1)
                    {
                        testRecord.CreateDate = new DateTime(2050, 5, 10);
                        testRecord.AnotherField = true;
                    }
                    else
                    {
                        testRecord.CreateDate = new DateTime(2015, 5, 10);
                        testRecord.AnotherField = false;
                    }


                    testRecords[count] = testRecord;
                }

                StringBuilder sBuilder = new StringBuilder();
                System.IO.StringWriter sWriter = new System.IO.StringWriter(sBuilder);
                XmlSerializer serializer = new XmlSerializer(typeof(UserTable[]));
                serializer.Serialize(sWriter, testRecords);             

                using (SqlConnection con = new SqlConnection(connectionString))
                {
                    string sprocName = "spTEST_UpdateTEST_TEST";

                    using (SqlCommand cmd = new SqlCommand(sprocName, con))
                    {
                        cmd.CommandType = CommandType.StoredProcedure;

                        cmd.CommandType = System.Data.CommandType.StoredProcedure;

                        SqlParameter param1 = new SqlParameter("@UpdatedProdData", SqlDbType.VarChar, int.MaxValue);
                        param1.Value = sBuilder.Remove(0, 41).ToString();
                        cmd.Parameters.Add(param1);
                        con.Open();
                        int result = cmd.ExecuteNonQuery();
                        con.Close();
                    }
                }
            }

@Fredrik Johansson,我不确定你的建议是否可行。在我看来,你似乎希望我为每个记录制作更新语句。但我不能这样做,因为我需要更新1到50,000多条记录,而且在那时之前我不会知道具体数量。

编辑3

所以现在我的存储过程是这样的。我认为它应该能够处理并发连接,但我想确保一下。

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[sp_MassUpdate]
@BatchNumber uniqueidentifier 
AS
BEGIN
    update Product
    set ProductQty = 50
    from Product prod
    join StagingTbl stage on prod.ProductId = stage.ProductId
    where stage.BatchNumber = @BatchNumber

    DELETE FROM StagingTbl
    WHERE BatchNumber = @BatchNumber

END

你能解释/证明一下你关于LINQ-to-SQL更新性能问题的评论吗? - Alastair Pitts
基本上就是这样。我想插入500条记录并更新500条记录。验证需要1分58秒,使用批量复制进行插入只需要2秒,使用linq to sql更新所有500行并插入它们需要4分钟。我想把它降下来,因为我可能需要更新30,000到50,000行。但是我不想使用linq to sql进行选择和操作,因为我发现使用对象更容易处理。当涉及到更新时,我不在乎它的外观,因为如果我已经操纵了所有记录,我可以轻松地提取它们。 - chobo2
使用for循环遍历LINQ对象的每个值,并将其投入所需的任何内容中。 - chobo2
2
很有趣,您使用L2S时遇到了这种性能问题。可能是AutoSync设置导致更新时间非常长。您是否使用SQL Profiler检查过更新期间发生的情况?如果您看到每个更新语句后面都有一个select语句之类的东西,那么我建议尝试更改Linq to SQL对象上的AutoSync属性。 - Jeff Schumacher
@chobo2:您能否提供几行代码来说明您的问题?据我所知,您正在从 SQL 服务器下载大约 500 行数据到 DataTable 中,然后想在客户端更新这些数据,最后将这些更改提交到 SQL 服务器。是这样吗?- 无论如何,您可以跟踪这些更改,然后以单个命令将它们发送到数据库:“BEGIN TRANSACTION \r\nUPDATE table SET column1=value1 WHERE id=123\r\nUPDATE table SET column2=value2 WHERE id=456\r\nCOMMIT TRANSACTION” - Fredrik Johansson
你可以批量插入到一个临时表中,这样你就不必担心批次号的问题。如果在调用存储过程之前创建临时表,它将可以访问该表。 - Sam Saffron
5个回答

2
您可以使用sqlDataAdapter来进行批量更新。不管您如何填充数据集,无论是L2SQL还是其他方式,您都可以使用不同的方法来进行更新。只需使用数据表中的数据定义要运行的查询即可。
关键在于UpdateBatchSize。数据适配器将按您定义的大小分批发送更新。您需要尝试使用该值来确定最佳数量,但通常500-1000个为最好的选择。然后,SQL可以优化更新并更快地执行。请注意,在进行批量更新时,不能更新数据表的行源。
我使用此方法来更新10-100K的数据,通常在2分钟内完成。但这取决于您要更新的内容。
抱歉,这是VB...
Using da As New SqlDataAdapter
      da.UpdateCommand = conn.CreateCommand
      da.UpdateCommand.CommandTimeout = 300

      da.AcceptChangesDuringUpdate = False
      da.ContinueUpdateOnError = False
      da.UpdateBatchSize = 1000 ‘Expirement for best preformance
      da.UpdateCommand.UpdatedRowSource = UpdateRowSource.None 'Needed if UpdateBatchSize > 1
      sql = "UPDATE YourTable"
      sql += " SET YourField = @YourField"
      sql += " WHERE ID = @ID"
      da.UpdateCommand.CommandText = sql
      da.UpdateCommand.UpdatedRowSource = UpdateRowSource.None
      da.UpdateCommand.Parameters.Clear()
      da.UpdateCommand.Parameters.Add("@YourField", SqlDbType.SmallDateTime).SourceColumn = "YourField"
      da.UpdateCommand.Parameters.Add("@ID", SqlDbType.SmallDateTime).SourceColumn = "ID"

      da.Update(ds.Tables("YourTable”)
End Using

另一个选项是批量复制到临时表,然后运行查询从中更新主表。这可能更快。

1

1

正如allonym所说,使用SqlBulkCopy非常快(我发现速度提高了200倍以上——从1500秒到6秒)。但是您可以使用DataTable和DataRows类来提供数据给SqlBulkCopy(这似乎更容易)。以这种方式使用SqlBulkCopy还具有.NET 3.0兼容性的优点(Linq仅在3.5中添加)。 请查看http://msdn.microsoft.com/en-us/library/ex21zs8x%28v=VS.100%29.aspx获取一些示例代码。


0

你必须直接使用表达式树进行工作,但这是可行的。事实上,已经为您完成了这项工作,您只需下载源代码:

使用LINQ to SQL批量更新和删除

另一种选择是仅使用存储过程或使用DataContextExecuteMethodCallExecuteCommand方法的自定义SQL查询。


你能解释一下这个替代方案吗?我不在乎更新部分是在存储过程还是ado.net中,我只关心到那个点为止,我宁愿在linq to sql中进行选择和操作,以便我可以处理对象。当我完成操作后,我不在乎它们如何更新到数据库。 - chobo2
@chobo2:我不确定有什么需要解释的-如果您可以完全在服务器上编写更新逻辑,作为存储过程,那么只需将该SP拖到Linq to SQL设计器表面并运行即可。但是,如果您要对一千条记录进行不同的更新,则无法逐个记录进行更新;加速此过程的唯一方法是使用Table-Valued Parameter或批量插入到暂存表中,而Linq to SQL都不支持。 - Aaronaught
@chobo2:创建一个与您要更新的表具有相似结构的永久表,使用SqlBulkCopybcp工具进行更新记录的BULK INSERT,然后运行一个存储过程,该存储过程从暂存表执行MERGE到生产环境(或在SQL-2008之前,只需使用UPDATE FROM...INNER JOIN)。 - Aaronaught
你有这个内部连接更新的示例/教程吗?那么这个暂存表是一个完全复制的表,对吗?在插入完成后,您会清除此暂存表吗?如果是这样,如果其他人在您进行更新时向此暂存表添加记录,那么它们不会丢失吗?顺便说一下,我正在使用2005。 - chobo2
嗯,我对这些扩展真的没有兴趣。我可以一直使用linq直到大规模插入的时候,然后我觉得更新的存储过程可能不会那么难(可能比制作这些扩展方法要容易得多)。我想出了一个存储过程,但我不确定它能处理并发连接。 - chobo2
显示剩余3条评论

0

即使数据表是手动/通过linq或其他任何来源进行编程填充的,您仍可以使用SqlDataAdapter进行批量更新。

只需记得手动为数据表中的行设置RowState。对此,请使用dataRow.SetModified()


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接