EF Core 慢速批量插入(~80k 行)
Posted
技术标签:
【中文标题】EF Core 慢速批量插入(~80k 行)【英文标题】:EF Core Slow Bulk Insert (~80k rows) 【发布时间】:2020-05-14 04:25:03 【问题描述】:我有一个Save
对象,它关联了多个集合。对象的总大小如下:
对象之间的关系可以从这个映射中推断出来,并且似乎在数据库中正确表示。查询也很好。
modelBuilder.Entity<Save>().HasKey(c => c.SaveId).HasAnnotation("DatabaseGenerated",DatabaseGeneratedOption.Identity);
modelBuilder.Entity<Save>().HasMany(c => c.Families).WithOne(x => x.Save).HasForeignKey(x => x.SaveId);
modelBuilder.Entity<Save>().HasMany(c => c.Countries).WithOne(x => x.Save).HasForeignKey(x => x.SaveId);
modelBuilder.Entity<Save>().HasMany(c => c.Provinces).WithOne(x => x.Save).HasForeignKey(x => x.SaveId);
modelBuilder.Entity<Save>().HasMany(c => c.Pops).WithOne(x => x.Save).HasForeignKey(x => x.SaveId);
modelBuilder.Entity<Country>().HasOne(c => c.Save);
modelBuilder.Entity<Country>().HasMany(c => c.Technologies).WithOne(x => x.Country).HasForeignKey(x => new x.SaveId, x.CountryId);
modelBuilder.Entity<Country>().HasMany(c => c.Players).WithOne(x => x.Country).HasForeignKey(x => new x.SaveId, x.CountryId);
modelBuilder.Entity<Country>().HasMany(c => c.Families).WithOne(x => x.Country).HasForeignKey(x => new x.SaveId, x.OwnerId);
modelBuilder.Entity<Country>().HasMany(c => c.Provinces).WithOne(x => x.Owner);
modelBuilder.Entity<Country>().HasKey(c => new c.SaveId, c.CountryId );
modelBuilder.Entity<Family>().HasKey(c => new c.SaveId, c.FamilyId );
modelBuilder.Entity<Family>().HasOne(c => c.Save);
modelBuilder.Entity<CountryPlayer>().HasKey(c => new c.SaveId, c.CountryId, c.PlayerName );
modelBuilder.Entity<CountryPlayer>().HasOne(c => c.Country);
modelBuilder.Entity<CountryPlayer>().Property(c => c.PlayerName).HasMaxLength(100);
modelBuilder.Entity<CountryTechnology>().HasKey(c => new c.SaveId, c.CountryId, c.Type );
modelBuilder.Entity<CountryTechnology>().HasOne(c => c.Country);
modelBuilder.Entity<Province>().HasKey(c => new c.SaveId, c.ProvinceId );
modelBuilder.Entity<Province>().HasMany(c => c.Pops).WithOne(x => x.Province);
modelBuilder.Entity<Province>().HasOne(c => c.Save);
modelBuilder.Entity<Population>().HasKey(c => new c.SaveId, c.PopId );
modelBuilder.Entity<Population>().HasOne(c => c.Province);
modelBuilder.Entity<Population>().HasOne(c => c.Save);
我从一个文件中解析了整个save
,所以我不能一个一个地添加所有的集合。解析后,我有一个 Save
及其所有关联的集合,最多可添加 80k 个对象,这些对象都不存在于数据库中。
然后,当我调用dbContext.Add(save)
时,处理大约需要 44 秒,RAM 使用量从 100mb 上升到大约 700mb。
然后,当我调用 dbContext.SaveChanges()
(我还尝试了 EF Extensions 中的常规 BulkSaveChanges()
方法,没有显着差异)时,它需要额外的 60 秒,RAM 使用量上升到 1.3Gb。
这里发生了什么?为什么这么长和这么多的内存使用?实际上传到数据库只需要大约最后 5 秒。
PS:我也尝试过禁用更改检测但没有任何效果。
PS2:实际用法和cmets中要求的完整代码:
public class HomeController : Controller
private readonly ImperatorContext _db;
public HomeController(ImperatorContext db)
_db = db;
[HttpPost]
[RequestSizeLimit(200000000)]
public async Task<IActionResult> UploadSave(List<IFormFile> files)
[...]
await using (var stream = new FileStream(filePath, FileMode.Open))
var save = ParadoxParser.Parse(stream, new SaveParser());
if (_db.Saves.Any(s => s.SaveKey == save.SaveKey))
response = "The save you uploaded already exists in the database.";
else
_db.Saves.Add(save);
_db.BulkSaveChanges();
[...]
【问题讨论】:
请显示实际代码,并显示您如何使用BulkSaveChanges()
欢迎来到 ORM 的奇妙世界。数据访问代码太重要且性能至关重要,无法由机器设计。
实体框架不是批量操作的最佳选择。也许,在这种情况下,尝试创建一个存储过程并将数据作为 json 传递?我不知道 MariaDB。您仍然可以通过 EF 调用存储过程。
@holger 只有一次保存。 Any 搜索非常快,因为它由 ef 转换为 sql 中的存在选择。问题不存在
我明白你为什么要一次性保存完整的对象图,但正如你所说,它不符合你的性能预期,所以你必须找到另一种方法。如何将文件加载到“不同”对象图中,然后分别提取不同的“表”,然后将它们存储到数据库中,这意味着您一次可以进行 1000 次部分插入。
【参考方案1】:
编辑:1. 确保问题不是数据库。
执行你自己的命令,看看它的运行速度。
-
通过为每个工作单元使用新上下文来保持活动上下文图较小,同时尝试关闭 AutoDetechChangesEnabled
3.将多个命令批处理在一起
这是Entity Framework and slow bulk INSERTs上的一篇好文章
【讨论】:
它不是 SQL(顺便说一下它的 MariaDB),因为缓慢的部分从尚未联系数据库的 Add 方法开始 对不起,我错过了那部分,只是自动假设它是sql
。同样的原则仍然适用,因为它仍然可以帮助提高性能。你能告诉我们所有相关的代码吗?那会更有帮助。
我添加了完整的代码,希望它可以帮助您理解问题;)
谢谢,还有几个问题。 BulkSaveChanges
是做什么的?我想尝试找到一个与数据库往返次数最少的解决方案。你有没有尝试过这样的事情? entityframeworkcore.com/saving-data-bulk-insert?它为您做到这一点并且易于使用。
BulkSaveChanges 来自您链接我的同一个库。不过我也会测试批量插入。【参考方案2】:
从 nugets 下载 EFCore.BulkExtensions
删除“_db.BulkSaveChanges();”并替换“_db.Saves.Add(save);”使用此代码
_db.Saves.BulkInsert(save);
【讨论】:
以上是关于EF Core 慢速批量插入(~80k 行)的主要内容,如果未能解决你的问题,请参考以下文章