【问题标题】:Entity framework large data set, out of memory exception实体框架大数据集,内存不足异常
【发布时间】:2013-08-11 07:46:39
【问题描述】:

我正在处理一个非常大的数据集,大约有 200 万条记录。我有下面的代码,但是在处理了大约三批(大约 600,000 条记录)后出现内存不足异常。我知道,当它遍历每个批处理实体框架延迟加载时,它会尝试将完整的 200 万条记录构建到内存中。有什么办法可以卸载我处理过的那一批吗?

ModelContext dbContext = new ModelContext();
IEnumerable<IEnumerable<Town>> towns = dbContext.Towns.OrderBy(t => t.TownID).Batch(200000);
foreach (var batch in towns)
{
    SearchClient.Instance.IndexMany(batch, SearchClient.Instance.Settings.DefaultIndex, "Town", new SimpleBulkParameters() { Refresh = false });
}

注意:Batch 方法来自本项目:https://code.google.com/p/morelinq/

搜索客户端是这样的:https://github.com/Mpdreamz/NEST

【问题讨论】:

  • 大量数据是我不确定 ORM 是否合适的工具的场景...
  • @Vadim,ORM 是处理业务逻辑的合适工具,无需担心底层数据存储,但是使用 ORM 编写批处理有更简单的方法。
  • @AkashKava,当然是。问题是,当您将 ORM 与大量数据相结合时,您总是会发现自己“取消”了在其他场景中您会认为是特性的各种 ORM 方面。在其他情况下,您以专门解决您正在使用的 ORM 问题的方式编写代码。我要说的是 - 鉴于大量数据 ORM 变得有问题。
  • @Vadim,它变得有问题只是因为它做错了,并不意味着它不应该被使用。永远不要在内存中一次处理大量数据,而应该使用正确的批处理方式。请参阅我的回答,我们每天都在使用 ORM 处理数百万条记录。大型操作必须分解为较小步骤的集合。
  • Batch 与 Skip(batchNo*batchSize).Take(batchSize) 有何不同?

标签: c# entity-framework


【解决方案1】:

问题在于,当您从 EF 获取数据时,实际上创建了两个数据副本,一个返回给用户,另一个由 EF 保留并用于更改检测(以便它可以将更改持久保存到数据库)。 EF 会在上下文的整个生命周期内保存第二组,而它的这组会耗尽您的内存。

你有两个选择来处理这个

  1. 每批次更新上下文
  2. 在查询中使用 .AsNoTracking() 例如:

    IEnumerable<IEnumerable<Town>> towns = dbContext.Towns.AsNoTracking().OrderBy(t => t.TownID).Batch(200000);
    

这告诉 EF 不要为更改检测保留副本。你可以在我的博客上阅读更多关于 AsNoTracking 的作用及其对性能的影响:http://blog.staticvoid.co.nz/2012/4/2/entity_framework_and_asnotracking

【讨论】:

  • NoTracking 是否保留导航属性?比如我想设置/添加相关对象,会保存吗?
  • @AkashKava 不,它不会,如果您想保存任何内容,您需要先将其附加回上下文,如果实体具有导航属性,则也需要附加这些属性。如果你真的想修改你加载的实体,我建议使用第一种方法。
  • 感谢@LukeMcGregor,我发现更新每批次的上下文效果非常好
  • 你如何更新你的上下文?就dbContext = new DbContext()?
  • @mrmashal IEnumerable 上的扩展方法,它返回一个 IEnumerable>,其中内部集合有 20000 个项目。我自己写的,但如果你想要类似的东西,它超级简单
【解决方案2】:

我编写了一个迁移例程,该例程从一个 DB 读取并写入(布局略有变化)到另一个 DB(不同类型),在这种情况下,更新每个批次的连接并使用 AsNoTracking() 没有切断给我。

请注意,使用 '97 版本的 JET 会出现此问题。它可以与其他数据库完美配合。

但是,以下算法确实解决了内存不足问题:

  • 一个连接用于读取,一个连接用于写入/更新
  • 使用 AsNoTracking() 读取
  • 每写入/更新 50 行左右,检查内存使用情况,根据需要恢复内存 + 重置输出数据库上下文(和连接的表):

    var before = System.Diagnostics.Process.GetCurrentProcess().VirtualMemorySize64;
    if (before > 800000000)
    {
        dbcontextOut.SaveChanges();
        dbcontextOut.Dispose();
        GC.Collect();
        GC.WaitForPendingFinalizers();
        dbcontextOut = dbcontextOutFunc();
        tableOut = Dynamic.InvokeGet(dbcontextOut, outputTableName);
    }
    

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多