实体框架性能问题,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。
【讨论】:
关闭此功能会有什么负面影响? @RayLovelessSaveChanges
会将每个插入实体中的每个字段提交到数据库。查询会更长,但可以说仍然比运行 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 很慢的主要内容,如果未能解决你的问题,请参考以下文章