【问题标题】:Entity framework performance issue, saveChanges is very slow实体框架性能问题,saveChanges 很慢
【发布时间】: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,那么提高性能的最佳方法是什么?

谢谢

【问题讨论】:

    标签: entity-framework


    【解决方案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 将照常跟踪查询点上。这似乎确实适用于大型数据集,所以我希望它会有所帮助。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-11-16
          • 2011-12-14
          • 2016-03-09
          相关资源
          最近更新 更多