我正在使用下面的扩展函数通过 EF Core 和
EFCore.BulkExtensions
更新插入数据,但问题是当我尝试插入 200 万条记录时此函数的执行大约需要 17 分钟,最近它抛出了此异常
无法为数据库“tempdb”中的对象“dbo.SORT 临时运行存储:140737501921280”分配空间,因为“PRIMARY”文件组已满。通过删除不需要的文件、删除文件组中的对象、向文件组添加其他文件或为文件组中的现有文件设置自动增长来创建磁盘空间。 由于“ACTIVE_TRANSACTION”,数据库“tempdb”的事务日志已满,并且保留 lsn 为 (41:136:347)
当我执行此功能时,我可以看到“C”分区的存储空间正在减少:
当我重新启动 SQL Server 时,可用空间变为大约 30 GB,我尝试使用多线程(并行)进行插入,没有注意到时间变化,那么您有什么建议,或者此处显示的代码是否有任何问题。
注意:即使有 200 万条记录,for 循环也不会花费太多时间。
public static async Task<OperationResultDto> AddOrUpdateBulkByTransactionAsync<TEntity>(this DbContext _myDatabaseContext, List<TEntity> data) where TEntity : class
{
using (var transaction = await _myDatabaseContext.Database.BeginTransactionAsync())
{
try
{
_myDatabaseContext.Database.SetCommandTimeout(0);
var currentTime = DateTime.Now;
// Disable change tracking
_myDatabaseContext.ChangeTracker.AutoDetectChangesEnabled = false;
// Set CreatedDate and UpdatedDate for each entity
foreach (var entity in data)
{
var createdDateProperty = entity.GetType().GetProperty("CreatedDate");
if (createdDateProperty != null && (createdDateProperty.GetValue(entity) == null || createdDateProperty.GetValue(entity).Equals(DateTime.MinValue)))
{
// Set CreatedDate only if it's not already set
createdDateProperty.SetValue(entity, currentTime);
}
var updatedDateProperty = entity.GetType().GetProperty("UpdatedDate");
if (updatedDateProperty != null)
{
updatedDateProperty.SetValue(entity, currentTime);
}
}
// Bulk insert or update
var updateByProperties = GetUpdateByProperties<TEntity>();
var bulkConfig = new BulkConfig()
{
UpdateByProperties = updateByProperties,
CalculateStats = true,
SetOutputIdentity = false
};
// Batch size for processing
int batchSize = 50000;
for (int i = 0; i < data.Count; i += batchSize)
{
var batch = data.Skip(i).Take(batchSize).ToList();
await _myDatabaseContext.BulkInsertOrUpdateAsync(batch, bulkConfig);
}
// Commit the transaction if everything succeeds
await transaction.CommitAsync();
return new OperationResultDto
{
OperationResult = bulkConfig.StatsInfo
};
}
catch (Exception ex)
{
// Handle exceptions and roll back the transaction if something goes wrong
transaction.Rollback();
return new OperationResultDto
{
Error = new ErrorDto
{
Details = ex.Message + ex.InnerException?.Message
}
};
}
finally
{
// Re-enable change tracking
_myDatabaseContext.ChangeTracker.AutoDetectChangesEnabled = true;
}
}
}
看起来手动批处理会产生更多的启动成本。另外,您正在使用
CalculateStats = true
,这意味着每次运行都会毫无意义地重新计算统计数据。
SqlBulkCopy
,这是 BulkExtensions 在底层使用的功能,具有内置批处理功能,您可以在批量配置中使用它。您不需要自己批处理。然后它会在最后计算统计数据(或者您可以将其关闭)。
此外,显式事务意味着数据库事务日志以及
tempdb
数据库中的任何工作表和行版本控制在提交之前无法清除。您可能想要一个干净的回滚,但这确实是有代价的。如果你确实需要它,只需将配置中的批量大小设置为 0,你就会得到一笔大的内部交易。