C#/ SQL Server 2016 - 更新大约具有哈希值的1亿条记录

时间:2017-05-29 12:13:55

标签: c# .net sql-server hash

我需要能够从SQL Server 2016数据库表中读取大约1亿条记录,然后为一个或多个列生成哈希值并将这些记录写回表中。

到目前为止,我已经尝试了一些对我们的需求来说太慢的解决方案。我在带有i7-7700HQ处理器和32GB RAM的戴尔XPS 15上进行测试。首先,我尝试使用带有SHA1哈希的T-SQL HASHBYTES()函数,但这在1亿个记录测试数据集上花了不少时间。

使用C#OleDbReader进行更新当然更慢,但瓶颈似乎是写入记录。现在,我正在使用SqlBulkCopy将更改的记录复制到新的临时表中,这比更新现有表要快得多。但是仍然需要40分钟来生成所有散列并且写回记录需要多倍。理想情况下,我们希望整个操作在一小时内完成。有没有人知道我可以进一步优化的地方。这是代码:

using (SqlConnection sourceConnection = new SqlConnection(connectionString))
{
sourceConnection.Open();

SqlDataAdapter adapter = new SqlDataAdapter("SELECT *  FROM [ContosoRetailDW].[dbo].[FactInventory]", sourceConnection);

var dataTable = new System.Data.DataTable();

adapter.FillSchema(dataTable, System.Data.SchemaType.Source);              

dataTable.Columns[2].DataType = typeof(string);
dataTable.Columns[2].MaxLength = 20;

adapter.Fill(dataTable);

for (int i = 0; i < dataTable.Rows.Count; i++)
{
    byte[] toHash = Encoding.UTF8.GetBytes((string)dataTable.Rows[i][2]);
    dataTable.Rows[i][2] = xxHash.CalculateHash(toHash).ToString("X");
}

using (SqlConnection destinationConnection = new SqlConnection(connectionString))
{
    destinationConnection.Open();

    using (SqlBulkCopy bulkCopy = new SqlBulkCopy(destinationConnection.ConnectionString))
    {
        bulkCopy.BatchSize = 100000;
        bulkCopy.DestinationTableName = "FactInventory_Hashed";
        bulkCopy.WriteToServer(dataTable);
    }
}

我已经尝试过使用批量复制批量大小,我使用的是非常快速的哈希算法。

1 个答案:

答案 0 :(得分:1)

这或多或少是一个扩展评论。

从我的观点来看,这种方法有以下缺点:

[1]它将大量数据移到SQL Server SQL DB -> C# App -> SQL DB之外,这会生成大量的网络I / O(它的网络I / O - 是 - 但它仍然很多I / O)

[2]它将大量数据加载到内存中。如果此应用程序在与SQL Server数据库引擎相同的计算机上执行,则可能会对在同一台计算机上运行的其他应用程序(包括此处的SQL Server)产生内存压力。

[3]它会在大概的大表上生成两次扫描。第一个是SELECT ... FROM ...,第二个是for (...) {...}语句。

我会尝试什么?

(i)我会将xxHash.CalculateHash方法导入CLR scalar function(参见https://docs.microsoft.com/en-us/sql/relational-databases/clr-integration-database-objects-user-defined-functions/clr-scalar-valued-functions

CREATE ASSEMBLY CLRxxHash FROM 'D:\....\xxHash.dll';  
GO  

CREATE FUNCTION dbo.xxHash(@Source VARBINARY(8000)) RETURNS INT   
AS EXTERNAL NAME CLRxxHash.......;   
GO 

然后我将使用以下T-SQL脚本

USE ContosoRetailDW
GO

INSERT dbo.FactInventory_Hashed (..., HashCol)
SELECT ..., dbo.xxHash(CONVERT(VARCHAR(8000), fi.Column2) AS HashCol
FROM dbo.FactInventory AS fi

警告:如果SQL Server使用并行性,这应该在大型表上运行得足够好(至少)。由于标量函数,SQL Server可能会生成一个串行计划。

(ii)如果上述INSERT ... SELECT语句的执行计划是连续的,那么我会使用批次:

一个连接将处理IDs1之间1.000.000的所有行

INSERT dbo.FactInventory_Hashed (..., HashCol)
SELECT ..., dbo.xxHash(CONVERT(VARCHAR(8000), fi.Column2) AS HashCol
FROM dbo.FactInventory AS fi
WHERE fi.ID BETWEEN 1 AND 1000000

另一个连接将处理IDs1.000.001

之间的所有2.000.000
INSERT dbo.FactInventory_Hashed (..., HashCol)
SELECT ..., dbo.xxHash(CONVERT(VARCHAR(8000), fi.Column2) AS HashCol
FROM dbo.FactInventory AS fi
WHERE fi.ID BETWEEN 1000001 AND 2000000

等等。