我需要能够从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);
}
}
我已经尝试过使用批量复制批量大小,我使用的是非常快速的哈希算法。
答案 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
语句的执行计划是连续的,那么我会使用批次:
一个连接将处理IDs
和1
之间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
另一个连接将处理IDs
和1.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
等等。