百万级数据高性能批量操作

小占时光 2025-04-16 14:48:13 24


介绍

在开发企业级应用或数据密集型系统时,我们经常会遇到大批量数据的插入、更新或删除操作。使用 Entity Framework Core(简称 EF Core)默认的方式处理这类操作时,性能往往会成为瓶颈。平时大多时候都是简单的CURD,但数据量较大时就不能平时处理方法了。

平时用用ABP框架,对数据的CURD都非常简单,直接使用框架提供的Repository即可。例如插入多条数据时我们会这样写:

await Repository.InsertManyAsync(list);

如果遇到上万数据基本就用不了。因为:

  • 默认 InsertManyAsync 是逐条插入;
  • 插入时每条记录可能会触发审计日志、领域事件、验证等;
  • EF Core 的 SaveChangesAsync 在批量操作中并不高效。

.net对于上万以上的数据操作可以使用EFCore.BulkExtensions

EFCore.BulkExtensions 

Github地址:➤ https://github.com/abpframework/abp

EFCore.BulkExtensions 能够解决的问题:

  • 使用 SqlBulkCopy 等底层机制,批量执行数据库操作
  • 避免 ChangeTracker,提升操作效率
  • 极大减少数据库连接的往返次数

EFCore.BulkExtensions 机制:

  • 插入(Insert):使用 SqlBulkCopy
  • 更新/删除/合并:使用 MERGE SQL 语法
  • 避免 EF Core 的 ChangeTracker
  • 可配置批次大小、事务处理、ID输出等选项

这使得它相比 EF Core 原生操作,执行效率提升数倍至数百倍。

提供的主要方法

BulkInsertAsync批量插入数据
BulkUpdateAsync批量更新数据
BulkDeleteAsync批量删除数据
BulkMergeAsync插入或更新(UPSERT)
BulkReadAsync从数据库中批量读取数据

使用实例

在开发过程中系统有一个数据导入功能,数据都百万条以上,因为系统是第一版,就先考虑将功能实现,并未考虑分表和分库的做法,就全部存到一个数据。先导入17万条数数据,但在下面插入代码的地方就动。

await Repository.InsertManyAsync(list);

改用EFCore.BulkExtensions进行批量操作。

使用安装命令进行安装或者nuget包管理器中输入EFCore.BulkExtensions进行安装。

dotnet add package EFCore.BulkExtensions

值得注意的是这里,需要安装.net 对应版本,例如我使用.net8 安装的版本就需要是8.x的。

安装好就可以使用了,这里使用新增和更新来做示例。

 public async Task BatchInsertAsync(List<Poi> entities)
 {
     var dbContext = await GetDbContextAsync();
     await dbContext.BulkInsertAsync(entities, new BulkConfig
     {
         BatchSize = 5000, // 批量插入的大小
         SetOutputIdentity = false // 如果不需要返回自增ID

     });
 }

 public async Task BatchUpdateAsync(List<Poi> entities)
 {
     var dbContext = await GetDbContextAsync();
     await dbContext.BulkUpdateAsync(entities, new BulkConfig
     {
         SetOutputIdentity = false,
         UseTempDB = true, // 使用临时表提高性能
         PreserveInsertOrder = false, // 不保留插入顺序
         EnableStreaming = true, // 启用流式处理
         TrackingEntities = false, // 不跟踪实体变化
         BulkCopyTimeout = 0, // 无超时限制
         SqlBulkCopyOptions = SqlBulkCopyOptions.Default | SqlBulkCopyOptions.TableLock
     });
 }

下面是代码运行后的效果:

18万(接近18万)条数据插入数据消耗时间11734 毫秒 (11.734 秒)

200万(接近200万)条数据116523 毫秒(1 分 56 秒)

200万条数据测试更新时不能直接写,会超时,虽然已经设置了不限制超时时间,但还是会提超时。

await _testRepository.BatchUpdateAsync(batch);

使用分批次更新,则可以继续执行

// 记录时间
var stopwatch = new Stopwatch();
stopwatch.Start();

const int batchSize = 10000;
foreach (var batch in updateEntities.Chunk(batchSize))
{
    await _testRepository.BatchUpdateAsync(batch.ToList());
}

stopwatch.Stop();
Console.WriteLine($"插入和更新完成,用时:{stopwatch.Elapsed.TotalSeconds}秒");

200万数据更新399940 毫秒 ( 6 分 39 秒)

测试结果和官网有一定差异,我使用的表是真实项目表,表中有40多个字段,字段内容数据较多,且电脑性能不一样。

一般框架都是为了处理通用业务,封装的CRUD也是针对通用业务的,对于大数据处理还是需要自己写一下的。EFCore.BulkExtensions 是 EF Core 在处理高性能数据写入时的重要补充。它弥补了 EF Core 在大批量数据处理方面的不足,为系统提供了数倍甚至数百倍的性能提升。

最后一次修改 : 2025/4/19 上午2:54:42

优秀
123
分享
123
奶茶
123
文章版权声明:版权归原作者(小占时光)所有。未经明确书面许可,严禁任何个人或组织以任何形式进行商业性或非商业性的使用、转载或抄袭。
评论