在单个事务中针对不同表的多个并行(异步)SqlBulkCopy 插入的性能
Performance of Multiple Parallel (async) SqlBulkCopy inserts, against different tables, in a single Transaction
TL;DR
为什么 运行ning 多个 SqlBulkCopy 插入,针对不相关的 tables,async
& 并行地,在单个事务上似乎表现得好像它是运行宁串联?
上下文
我有一些代码正在计算和存储大量数据。
计算是预先完成的,所以代码的存储部分得到了要存储的一大堆数据。
我的数据库写入是使用 SqlBulkCopy.WriteToServerAsync
完成的,总的来说,它做得很好。
在我需要存储的东西中,有 6 个 table 与业务相关,但与 SQL 无关。因此,我对他们的写入需要在一个事务中,这样任何一个写入的错误都会恢复对所有其他写入的写入。
此代码的性能相当关键,因此我希望能够并行 运行 BulkInserts。没有 FKey 或任何其他 table 正在与之交互(数据完整性由代码管理)所以我看不出有任何理由认为这不可能。
我目前写的
我以为我知道如何编写所有代码并且能够使它们全部工作,但是有一个我不明白的奇怪的性能下降:
很高兴提供您想要的实际代码,但这已经是一个很长的 Q,而且代码会很长到 0。 LMK 如果你想看什么。
我会写:
“批量插入每个 table 顺序 ,全部在 单个事务 中”。
- 即我打开一个
new SqlConnection()
和 .BeginTransaction()
,
- 然后我
foreach
超过 6 tables,并且 await InsertToTable(transaction)
每个 table 在 foreach
移动到下一个之前。
- 当
foreach
结束时,我 .Commit()
交易并关闭连接。
- 我有一个大容量测试,运行这个版本在 184 秒(95%,+/- 2.45 秒)内。
“批量插入每个 table 顺序 ,每个 table 新连接和事务."
- 即我
foreach
超过 6 table 秒,await InsertToTable()
每个 table 在 foreach
移动到下一个之前。
- 在每个
InsertToTable()
调用中,我打开一个新的 SqlConnection
和 BeginTransaction
,然后在从方法返回之前我 .Commit()
和 .Close()
。
- 我有一个大容量测试,运行这个版本在 185 秒(95%,+/- 3.34s)内。
“BulkInsert into each table in parallel, with a new connection & Transaction for each table .
- 即我通过为每个 table 调用
thisTableTask = InsertToTable()
并捕获 Task
s 但 not await
ing 来启动所有 6 个任务(还)。
- 我
await Task.WhenAll()
捕获了6个任务。
- 在每个
InsertToTable()
调用中,我打开一个新的 SqlConnection
和 BeginTransaction
,然后在从方法返回之前我打开 .Commit()
和 .Close()
。 (但请注意,foreach 已移至下一个 table,因为它不会立即 await
任务。
- 我有一个大容量测试,运行这个版本在 144 秒(95%,+/- 5.20 秒)内。
“批量插入并行中的每个table,全部在单个事务中”。
- 即我打开一个
new SqlConnection()
和 .BeginTransaction()
.
- 然后我通过为每个 table 调用
thisTableTask = InsertToTable(transaction)
并捕获 Task
但 不是 await
他们(还)。
- 我
await Task.WhenAll()
捕获了6个任务。
WhenAll
结束后,我 .Commit()
交易并关闭连接。
- 我有一个大容量测试,运行这个版本在 179 秒(95%,+/- 1.78s)内。
在所有情况下,最终的 BulkInsert 看起来像:
using (var sqlBulk = BuildSqlBulkCopy(tableName, columnNames, transactionToUse))
{
await sqlBulk.WriteToServerAsync(dataTable);
}
private SqlBulkCopy BuildSqlBulkCopy(string tableName, string[] columnNames, SqlTransaction transaction)
{
var bulkCopy = new SqlBulkCopy(transaction.Connection, SqlBulkCopyOptions.Default, transaction)
{
BatchSize = 10000,
DestinationTableName = tableName,
BulkCopyTimeout = 3600
};
foreach (var columnName in columnNames)
{
// Relies on setting up the data table with column names matching the database columns.
bulkCopy.ColumnMappings.Add(columnName, columnName);
}
return bulkCopy;
}
当前性能统计数据
如上所述
- 连续+单Tran = 184s
- 顺序+单独传输=185s
- 并联+分离Tran = 144s
- 并联+单Tran = 179s
前 3 个结果对我来说都很有意义。
#1 vs #2:只要插入都能正常工作,事务就不会做太多事情。数据库仍在相同的时间点做所有相同的工作。
#2 vs #3:这是 运行 并行插入的全部要点。通过 运行 并行插入,我们花更少的时间等待 SQL 完成这件事。我们让 DB 并行执行大量工作,因此虽然没有 6 倍的加速那么多,但也足够了。
问题:
为什么最后一个案例这么慢?我可以修复它吗?
- 平行+单Tran = 179
这几乎与串行执行一样慢,并且比并行执行慢 25%,但是有多个事务!
这是怎么回事?
为什么 运行ning 多个 SqlBulkCopy 插入,而不是不相关的 tables,async
& 在单个事务上并行插入似乎表现得好像它是 运行ning 串联?
非受骗者:
SqlBulkCopy Multiple Tables Insert under single Transaction OR Bulk Insert Operation between Entity Framework and Classic Ado.net(运行不是并行查询)
(表是相关的,他们正试图从中读回)
Parallel Bulk Inserting with SqlBulkCopy and Azure(这是在谈论并行加载到 单个 table)
在同一 SQL 服务器 connection/transaction 上同时执行多个命令的唯一方法是使用 Multiple Active Result Sets (MARS)。 MARS 用于并行单一事务案例,因为您对每个并行批量复制使用相同的 connection/transaction。
MARS 执行 SELECT 并以交错而非并行方式插入批量操作,因此您将获得与串行执行大致相同的性能。您需要具有不同连接的分布式事务才能在同一事务范围内真正并行执行。
TL;DR
为什么 运行ning 多个 SqlBulkCopy 插入,针对不相关的 tables,async
& 并行地,在单个事务上似乎表现得好像它是运行宁串联?
上下文
我有一些代码正在计算和存储大量数据。 计算是预先完成的,所以代码的存储部分得到了要存储的一大堆数据。
我的数据库写入是使用 SqlBulkCopy.WriteToServerAsync
完成的,总的来说,它做得很好。
在我需要存储的东西中,有 6 个 table 与业务相关,但与 SQL 无关。因此,我对他们的写入需要在一个事务中,这样任何一个写入的错误都会恢复对所有其他写入的写入。
此代码的性能相当关键,因此我希望能够并行 运行 BulkInserts。没有 FKey 或任何其他 table 正在与之交互(数据完整性由代码管理)所以我看不出有任何理由认为这不可能。
我目前写的
我以为我知道如何编写所有代码并且能够使它们全部工作,但是有一个我不明白的奇怪的性能下降:
很高兴提供您想要的实际代码,但这已经是一个很长的 Q,而且代码会很长到 0。 LMK 如果你想看什么。
我会写:
“批量插入每个 table 顺序 ,全部在 单个事务 中”。
- 即我打开一个
new SqlConnection()
和.BeginTransaction()
, - 然后我
foreach
超过 6 tables,并且await InsertToTable(transaction)
每个 table 在foreach
移动到下一个之前。 - 当
foreach
结束时,我.Commit()
交易并关闭连接。 - 我有一个大容量测试,运行这个版本在 184 秒(95%,+/- 2.45 秒)内。
- 即我打开一个
“批量插入每个 table 顺序 ,每个 table 新连接和事务."
- 即我
foreach
超过 6 table 秒,await InsertToTable()
每个 table 在foreach
移动到下一个之前。 - 在每个
InsertToTable()
调用中,我打开一个新的SqlConnection
和BeginTransaction
,然后在从方法返回之前我.Commit()
和.Close()
。 - 我有一个大容量测试,运行这个版本在 185 秒(95%,+/- 3.34s)内。
- 即我
“BulkInsert into each table in parallel, with a new connection & Transaction for each table .
- 即我通过为每个 table 调用
thisTableTask = InsertToTable()
并捕获Task
s 但 notawait
ing 来启动所有 6 个任务(还)。 - 我
await Task.WhenAll()
捕获了6个任务。 - 在每个
InsertToTable()
调用中,我打开一个新的SqlConnection
和BeginTransaction
,然后在从方法返回之前我打开.Commit()
和.Close()
。 (但请注意,foreach 已移至下一个 table,因为它不会立即await
任务。 - 我有一个大容量测试,运行这个版本在 144 秒(95%,+/- 5.20 秒)内。
- 即我通过为每个 table 调用
“批量插入并行中的每个table,全部在单个事务中”。
- 即我打开一个
new SqlConnection()
和.BeginTransaction()
. - 然后我通过为每个 table 调用
thisTableTask = InsertToTable(transaction)
并捕获Task
但 不是await
他们(还)。 - 我
await Task.WhenAll()
捕获了6个任务。 WhenAll
结束后,我.Commit()
交易并关闭连接。- 我有一个大容量测试,运行这个版本在 179 秒(95%,+/- 1.78s)内。
- 即我打开一个
在所有情况下,最终的 BulkInsert 看起来像:
using (var sqlBulk = BuildSqlBulkCopy(tableName, columnNames, transactionToUse))
{
await sqlBulk.WriteToServerAsync(dataTable);
}
private SqlBulkCopy BuildSqlBulkCopy(string tableName, string[] columnNames, SqlTransaction transaction)
{
var bulkCopy = new SqlBulkCopy(transaction.Connection, SqlBulkCopyOptions.Default, transaction)
{
BatchSize = 10000,
DestinationTableName = tableName,
BulkCopyTimeout = 3600
};
foreach (var columnName in columnNames)
{
// Relies on setting up the data table with column names matching the database columns.
bulkCopy.ColumnMappings.Add(columnName, columnName);
}
return bulkCopy;
}
当前性能统计数据
如上所述
- 连续+单Tran = 184s
- 顺序+单独传输=185s
- 并联+分离Tran = 144s
- 并联+单Tran = 179s
前 3 个结果对我来说都很有意义。
#1 vs #2:只要插入都能正常工作,事务就不会做太多事情。数据库仍在相同的时间点做所有相同的工作。
#2 vs #3:这是 运行 并行插入的全部要点。通过 运行 并行插入,我们花更少的时间等待 SQL 完成这件事。我们让 DB 并行执行大量工作,因此虽然没有 6 倍的加速那么多,但也足够了。
问题:
为什么最后一个案例这么慢?我可以修复它吗?
- 平行+单Tran = 179
这几乎与串行执行一样慢,并且比并行执行慢 25%,但是有多个事务!
这是怎么回事?
为什么 运行ning 多个 SqlBulkCopy 插入,而不是不相关的 tables,async
& 在单个事务上并行插入似乎表现得好像它是 运行ning 串联?
非受骗者:
SqlBulkCopy Multiple Tables Insert under single Transaction OR Bulk Insert Operation between Entity Framework and Classic Ado.net(运行不是并行查询)
Parallel Bulk Inserting with SqlBulkCopy and Azure(这是在谈论并行加载到 单个 table)
在同一 SQL 服务器 connection/transaction 上同时执行多个命令的唯一方法是使用 Multiple Active Result Sets (MARS)。 MARS 用于并行单一事务案例,因为您对每个并行批量复制使用相同的 connection/transaction。
MARS 执行 SELECT 并以交错而非并行方式插入批量操作,因此您将获得与串行执行大致相同的性能。您需要具有不同连接的分布式事务才能在同一事务范围内真正并行执行。