【问题标题】:Best way to alter/update millions of SQL Server records using .NET (not T-SQL)?使用 .NET(不是 T-SQL)更改/更新数百万条 SQL Server 记录的最佳方法?
【发布时间】:2014-08-12 08:39:51
【问题描述】:

我有一个包含大约 800 万条记录的 SQL Server 表,用于一个研究项目,我需要为每条记录计算一些值并将这些值保存在记录中。计算相当复杂,所以我缺乏 T-SQL 知识意味​​着我想使用 .net 进行处理,尽管 T-SQL 脚本无疑是最好的选择。

我的方法很简单,但还是失败了。我正在使用数据阅读器(带有自己的连接)来遍历所有记录。我正在使用另一个连接来创建使用存储过程执行更新的命令。大约一百万后,它会因超时错误而失败。

我一开始尝试使用 EF,但速度很慢,即使我尝试了各种方法,我也很难防止内存不足异常。

使用 .net 执行此操作的最佳方法是什么?我是否使用错误的连接?我读过错误地使用多个连接会导致超时异常。

这是代码示例(VB.NET):

Public Sub UpdateRecords()

    Dim ReadConnection As New SqlConnection("ConnectionString")
    Dim WriteConnection As New SqlConnection("ConnectionString")
    Dim Reader As SqlDataReader
    Dim ReadCommand As SqlCommand
    Dim WriteCommand As SqlCommand
    Dim Transaction As SqlTransaction

    ReadConnection.Open()
    ReadCommand = New SqlCommand("select * from records", ReadConnection)
    Reader = ReadCommand.ExecuteReader
    If Reader.HasRows Then
        WriteConnection.Open()
        Transaction = WriteConnection.BeginTransaction
        Do While Reader.Read
            WriteCommand = New SqlCommand("StoredProcedure", WriteConnection, Transaction)
            'calculate
            'set parameters
            WriteCommand.ExecuteNonQuery()
        Loop
        Transaction.Commit()
        WriteConnection.Close()
    End If
    Reader.Close()
    ReadConnection.Close()

End Sub

【问题讨论】:

  • 使用 .net 执行此操作的最佳方法是什么? 学习 Transact-SQL。使用 SqlConnectionSqlCommand 执行更新数据的 Transact-SQL 查询。
  • 最好的方法是使用 T-SQL .... 并直接在服务器 !
  • 我知道这一点(正如我在问题中所说的那样),但是如果处理速度(它将在一个晚上运行一次)和最佳实践无关紧要(它是一个研究项目),但是我学习 T-SQL 的速度是一个问题,那么如何使用 .net 来完成呢?
  • 你能和我们分享一下计算吗?也许有人可以帮助您将计算转换为 T-SQL,因为从 .net 执行此操作可能会带来一些非常难看的超时和性能问题。一条一条地处理 800 万条记录将杀死您运行程序的服务器/计算机。您不仅需要计算每条记录的结果,还必须首先将这些记录流式传输到您的计算机...
  • 您的案例实际上需要 SQL 解决方案,因为 C#/VB 对于此类任务非常不适合。不如问我们如何编写您需要的 update 语句?

标签: .net sql-server vb.net


【解决方案1】:

您应该使用 .NET 中提供的 SqlBulkCopy 类。此类旨在满足您的需求。

以下代码将对您有所帮助。虽然我是即时写的,没有经过测试,但它会给你和如何处理这件事的想法。

Sub DoBulkCopy(ByVal sourceConnectionString As String, ByVal sourceTableSelectStatement As String, ByVal destinationConnectionString As String, ByVal destinationTableName As String)
    Try
        Using sourceConnection As New SqlConnection(SourceConnectionString)
            Using sourceCommand As New SqlCommand(sourceTableSelectStatement)
                sourceCommand.CommandTimeout = 0
                sourceCommand.Connection = sourceConnection
                sourceConnection.Open()
                Using sourceReader As SqlClient.SqlDataReader = sourceCommand.ExecuteReader
                    If sourceReader.HasRows Then
                        Using destinationConnection As New SqlConnection(destinationConnectionString)
                            destinationConnection.Open()
                            Using transaction As SqlTransaction = destinationConnection.BeginTransaction
                                Using bulkCopy As SqlBulkCopy = New SqlBulkCopy(destinationConnection, SqlBulkCopyOptions.Default, transaction)
                                    '' If the data source and the destination table have the same number of columns, and the ordinal position of each source column 
                                    '' within the data source matches the ordinal position of the corresponding destination column, the ColumnMappings collection is unnecessary. 
                                    '' However, if the column counts differ, or the ordinal positions are not consistent, you must use ColumnMappings to 
                                    '' make sure that data is copied into the correct columns.
                                    '' ** Uncomment and modify the following commented lines if necessary. **
                                    'With bulkCopy.ColumnMappings
                                    '    .Add("Column1", "Column1")
                                    'End With

                                    bulkCopy.DestinationTableName = destinationTableName
                                    bulkCopy.WriteToServer(sourceReader)
                                    transaction.Commit()
                                End Using
                            End Using
                            destinationConnection.Close()
                        End Using
                    End If
                End Using
                sourceConnection.Close()
            End Using
        End Using
    Catch ex As Exception
        MessageBox.Show(ex.Message, "Error!", MessageBoxButtons.OK, MessageBoxIcon.Error)
    End Try
End Sub

【讨论】:

    【解决方案2】:

    因为有这么多的记录,把所有的记录都加载出来再一个一个更新是不可行的。特别是如果您必须在 VB.NET 中进行计算。

    我建议您分批加载它们,计算每个批次,最后也分批更新它们(批次大小应设置为不会使每个批次的计算永远持续的值)。

    至于如何批量更新,我是going to leave this here。它讲述了如何使用结构化参数来更新/插入。

    其他选择包括:

    1. CLR assembly 中实现计算。这将允许您直接从 TSQL 调用它。因此,您将能够将整个过程移至数据库服务器。
    2. 正如其他人指出的那样,直接在 TSQL 中实现计算,但这取决于它们的复杂程度。它们可能会变得难以维护。

    【讨论】:

    • 好的,我希望有更简单的东西,但我明白了。但是,我将使用非常粗略的方法,即简单地使用 offset/fetch t-sql 来批量检索记录,并为每一个在本地存储数据并关闭连接,然后更新批次。它应该工作。它会很慢,但它会足够快:) 感谢您为我指明正确的方向。
    • @SuppaiKamo:没问题。这将是要走的路。
    【解决方案3】:

    至于你的问题是它工作了一段时间然后超时,我猜这是问题所在:

    Transaction = WriteConnection.BeginTransaction
        Do While Reader.Read
            WriteCommand = New SqlCommand("StoredProcedure", WriteConnection, Transaction)
            'calculate
            'set parameters
            WriteCommand.ExecuteNonQuery()
        Loop
    

    换句话说,您正在一次事务中逐一更新您的 800 万条记录。您很可能会破坏数据库中的事务日志。如果您是唯一使用此数据库的人(您声明这是一个研究项目),我会备份数据库并将transaction.commit() 移动到您的循环中或完全摆脱事务(因为 SQL Server 使用隐式事务,这些应该是同一件事)。如果出现问题,您的回滚就是数据库还原。

    一旦你扑灭了这场大火,我就开始学习 T-SQL;你只能使用错误的工具来完成这项工作。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-05-30
      • 1970-01-01
      相关资源
      最近更新 更多