【问题标题】:EF Core Slow Bulk Insert (~80k rows)EF Core 慢速批量插入(~80k 行)
【发布时间】: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 次部分插入。

标签: c# entity-framework entity-framework-core mariadb ef-core-3.1


【解决方案1】:

编辑: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);

【讨论】:

    猜你喜欢
    • 2017-01-20
    • 1970-01-01
    • 2021-10-21
    • 2011-08-07
    • 2016-05-22
    • 2015-07-03
    • 2020-08-07
    • 2013-10-14
    • 1970-01-01
    相关资源
    最近更新 更多