实体框架性能问题,saveChanges 很慢

Posted

技术标签:

【中文标题】实体框架性能问题,saveChanges 很慢【英文标题】:Entity framework performance issue, saveChanges is very slow 【发布时间】:2014-01-22 02:00:00 【问题描述】:

最近,我正在做一些简单的 EF 工作。非常简单, 首先,

List<Book> books = entity.Books.WHERE(c=>c.processed==false)ToList();  
then  
    foreach(var book in books)  
      
      //DoSomelogic, update some properties,   
    book.ISBN = "ISBN " + randomNumber();  
    book.processed = true;  
    entity.saveChanges(book)  
      

我把entity.saveChanges放在foreach里面,因为它是一个很大的列表,大约有100k条记录,如果这条记录被处理没有问题,那么标记这条记录,设置book.processed = true,如果进程被中断例外的话,下次我就不用再处理这些好记录了。

对我来说一切都很好。处理数百条记录时速度很快。然后当我们移动到 10 万条记录时, entity.saveChanges 非常非常慢。每条记录大约 1-3 秒。然后我们保留实体模型,但将entity.saveChanges 替换为经典的SqlHelper.ExecuteNonQuery("update_book", sqlparams)。而且速度非常快。

谁能告诉我为什么实体框架处理这么慢?如果我仍然想使用 entity.saveChanges,那么提高性能的最佳方法是什么?

谢谢

【问题讨论】:

【参考方案1】:

在执行插入之前关闭更改跟踪。这将显着提高您的性能(数量级)。将SaveChanges() 放在循环之外也会有所帮助,但关闭更改跟踪会更有帮助。

using (var context = new CustomerContext())

    context.Configuration.AutoDetectChangesEnabled = false;

    // A loop to add all your new entities

    context.SaveChanges();

有关更多信息,请参阅this page。

【讨论】:

关闭此功能会有什么负面影响? @RayLoveless SaveChanges 会将每个插入实体中的每个字段提交到数据库。查询会更长,但可以说仍然比运行 DetectChanges 更快,尤其是对于批量插入。 @RayLoveless Microsoft says 这“很容易将细微的错误引入您的应用程序”。当我尝试它时,我花了一段时间才弄清楚为什么我的更新完全停止工作。当我修复它时,它不再快了。我想它可能对其他场景有用。 没有变化!...仍然需要时间。我一次保存 1200 条记录,保存时间为 24 秒。 Rick Strahl 非常清楚地解释了解决此问题的几种方法:weblog.west-wind.com/posts/2014/dec/21/… TLDR:如果将更改写入数据库,则在每次迭代时重新创建 dbContext,否则关闭更改跟踪【参考方案2】:

我会将 SaveChanges(book) 放在 foreach 之外。由于书籍作为列表在实体上,因此您可以将其放在外面,EF 将更好地处理生成的代码。

列表是实体上的一个属性,EF 旨在优化后端数据库上的更新/创建/删除。如果你这样做,我会很好奇它是否有帮助。

【讨论】:

我之所以把savechanges放在foreach里,是因为记录比较大,运行时间比较昂贵(一般需要4-5个小时)。所以我们决定更新是否没有问题,我们将此记录标记为“已处理”。所以如果它崩溃或发生任何事情,我们不必再次重新更新“好”记录。 这是个好主意,但 Entity Framework 可能会以另一种方式更好地处理它。实体框架具有优化更新和插入的能力。当 savechanges() 被放置在一个循环中(比如你发布的那个循环中)时,它也倾向于尝试对整个记录进行完全提交。【参考方案3】:

我也可能建议您将 SaveChanges() 排除在循环之外,因为它会对数据库进行“n”次更新,因此上下文将有“n”次迭代所需的检查点和验证。

var books = entity.Books.Where(c => c.processed == false).ToList();

books.Foreach(b =>

    b.ISBN = "ISBN " + randomNumber();
    b.processed = true;
    //DoSomelogic, update some properties  
);
entity.SaveChanges();

【讨论】:

【参考方案4】:

AsNoTracking”对我有用

例如:

Item itemctx = ctx.Items.AsNoTracking().Single(i=>i.idItem == item.idItem);
ctx.Entry(itemctx).CurrentValues.SetValues(item);
itemctx.images = item.images;
ctx.SaveChanges();

如果没有“AsNoTracking”,更新会很慢。

【讨论】:

【参考方案5】:

在我看来,从性能和内存消耗的角度来看,实体框架对于 BULK 操作来说都是一个糟糕的选择。一旦超过几千条记录,SaveChanges 方法就真的开始崩溃了。

您可以尝试将您的工作划分为更小的事务,但同样,我认为您太努力了,无法创建它。

更好的方法是利用 DBMS 已经提供的 BULK 操作。 SQL Server 通过 .NET 提供 BULK COPY。 Oracle 为 Oracle.DataAccess 或非托管数据访问提供 BULK COPY。对于 Oracle.ManagedDataAccess,遗憾的是 BULK COPY 库不可用。但是我可以使用 BULK COLLECT/FOR ALL 创建一个 Oracle 存储过程,它允许我在几秒钟内插入数千条记录,并且在您的应用程序中占用更少的内存。在 .NET 应用程序中,您可以将 PLSQL 关联数组实现为参数等。

利用 DBMS 中的 BULK 功能的好处是减少了应用程序、查询处理器和数据库引擎之间的上下文切换。

我确信其他数据库供应商也提供类似的东西。

【讨论】:

【参考方案6】:

使用这个 Nuget 包: Z.EntityFramework.Extensions

它具有扩展方法,您可以在 DbContext 上调用,例如 DbContext.BulkSaveChanges,它的运行速度非常快。

注意:这不是免费包,但有试用期。

【讨论】:

它是付费套餐。【参考方案7】:

我只是直接执行插入命令。

//the Id property is the primary key, so need to have this update automatically    
db.Database.ExecuteSqlCommand("SET IDENTITY_INSERT[dbo].[MyTable] ON");
                            
foreach (var p in itemsToSave)

    db.Database.ExecuteSqlCommand("INSERT INTO[dbo].[MyTable]([Property1], [Property2]) VALUES(@Property1, @Property2)",
        new SqlParameter("@Property1", p.Property1),
        new SqlParameter("@Property2", p.Property2)"


db.Database.ExecuteSqlCommand("SET IDENTITY_INSERT[dbo].[MyTable] OFF");

运行速度非常快。 EF 在批量更新时速度慢得不可思议,在我的情况下,对于十几个左右的项目完全无法使用。

【讨论】:

请阅读问题:他们已经知道这一点,并且他们并不要求实体框架之外的替代方案。此外,这里不是关于插入,而是关于单独的更新语句。 原始问题似乎并未排除此答案作为提高性能的可能解决方案,并且对表运行 EF 查询会返回所有新项目,因此 EF 将照常跟踪查询点上。这似乎确实适用于大型数据集,所以我希望它会有所帮助。

以上是关于实体框架性能问题,saveChanges 很慢的主要内容,如果未能解决你的问题,请参考以下文章

实体框架 - SaveChanges 与事务

实体框架 SaveChanges 不保存数据

实体框架核心错误? SaveChanges 抛出选择

在 SaveChanges 的实体框架中出现错误

通过实体框架更新时如何绕过唯一键约束(使用 dbcontext.SaveChanges())

无法使用 DbContext 实体框架核心 SaveChanges()