【问题标题】:Iterating through IQueryable with foreach results in an out of memory exception使用 foreach 遍历 IQueryable 会导致内存不足异常
【发布时间】:2011-04-10 22:24:54
【问题描述】:

我正在使用 foreach / IQueryable 和 LINQ-to-SQL 遍历一个较小的 (~10GB) 表。 看起来像这样:

using (var conn = new DbEntities() { CommandTimeout = 600*100})
{
     var dtable = conn.DailyResults.Where(dr => dr.DailyTransactionTypeID == 1);
     foreach (var dailyResult in dtable)
     {
        //Math here, results stored in-memory, but this table is very small. 
        //At the very least compared to stuff I already have in memory. :)
     }
}

Visual Studio 调试器在 foreach 循环的底部不久后引发内存不足异常。我假设 dtable 的行没有被刷新。该怎么办?

【问题讨论】:

  • 您已经在大于 10GB 的内存中存储了什么?您是说 10 MB 吗?
  • 我在这台机器上有 16GB 的内存,但至少有一半的内存被 Windows bloat 和 SQL 缓存使用。我无法将 10GB 放入内存,所以我用完了它。我很惊讶 IQueryable 检索整个表...我希望它一次获取一个或少量行。
  • 我似乎能够通过将编译目标更改为 x64 而不是 x86 来解决这个问题,这会占用我机器上的更多内存。但是,我在 foreach 循环中迭代的数据并不大,所以我认为循环内的内容没有正确收集垃圾。

标签: c# linq linq-to-sql


【解决方案1】:

IQueryable<DailyResult>dtable 将在枚举时尝试将整个查询结果加载到内存中......在 foreach 循环的任何迭代之前。在 foreach 循环的迭代过程中它不会加载一行。如果您想要这种行为,请使用DataReader

【讨论】:

  • 现在我已经将表格导出到一个平面文件,并逐行读取。下次我会像专业人士一样使用 DataReader。 :)
  • 那么,当您在循环第一个查询的结果时执行另一个查询时,为什么在迭代 iqueryable(已经有一个与此命令关联的打开的 DataReader 必须首先关闭)时出现错误?
【解决方案2】:

你说~10GB 很小?你很有幽默感!

您可以考虑分块加载行,也就是分页。

conn.DailyResults.Where(dr => dr.DailyTransactionTypeID == 1).Skip(x).Take(y);

【讨论】:

  • 除非 OP 有 20 GB 的 RAM,否则这是处理这种情况的唯一方法。
  • 我不确定,你的意思是告诉我这种分页方法效率高吗?我很惊讶 IQueriable 想要将东西加载到内存中。我的意思是,为什么不把它做成某种数组来向无助的程序员表明它令人讨厌的意图。 :)
【解决方案3】:

除非有办法在 LINQ 中使用 DataReader,否则使用 DataReader 是一种倒退。我以为我们正试图摆脱 ADO。

上面建议的解决方案可行,但确实很难看。这是我的代码:

int iTake = 40000;
int iSkip = 0;
int iLoop;
ent.CommandTimeout = 6000;
while (true)
{
  iLoop = 0;
  IQueryable<viewClaimsBInfo> iInfo = (from q in ent.viewClaimsBInfo
                                       where q.WorkDate >= dtStart &&
                                         q.WorkDate <= dtEnd
                                       orderby q.WorkDate
                                       select q)
                                      .Skip(iSkip).Take(iTake);
  foreach (viewClaimsBInfo qInfo in iInfo)
  {
    iLoop++;
    if (lstClerk.Contains(qInfo.Clerk.Substring(0, 3)))
    {
          /// Various processing....
    }
  }
  if (iLoop < iTake)
    break;
  iSkip += iTake;
}

您可以看到我必须检查记录是否已用完,因为 foreach 循环将在 40,000 条记录处结束。不好。

2011 年 6 月 10 日更新:即使这样也行不通。在 2,000,000 条左右的记录中,我遇到了内存不足的异常。它也非常缓慢。当我修改它以使用 OleDB 时,它运行了大约 15 秒(而不是 10 多分钟)并且没有耗尽内存。有没有人可以快速运行的 LINQ 解决方案?

【讨论】:

  • 我不确定我是否遵循了一些更奇怪的部分,但想法是 => 查询、跳过、获取。太棒了,除了现在你遇到不同问题的部分 - 需要多少。也欢迎使用stackoverflow! :D
  • 格莱诺,谢谢。我不确定你认为“奇怪的部分”是什么,尽管“奇怪”似乎是我的中间名。 :) 不幸的是,如上所述,我回到了 ADO.Net。
【解决方案4】:

使用 .AsNoTracking() - 它告诉 DbEntities 不要缓存检索到的行

using (var conn = new DbEntities() { CommandTimeout = 600*100})
{
     var dtable = conn.DailyResults
                .AsNoTracking()      // <<<<<<<<<<<<<<
                .Where(dr => dr.DailyTransactionTypeID == 1);
     foreach (var dailyResult in dtable)
     {
        //Math here, results stored in-memory, but this table is very small. 
        //At the very least compared to stuff I already have in memory. :)
     }
}

【讨论】:

    【解决方案5】:

    我建议改用 SQL 来修改这些数据。

    【讨论】:

      猜你喜欢
      • 2013-06-22
      • 2014-06-07
      • 2012-12-15
      • 1970-01-01
      • 1970-01-01
      • 2020-11-28
      • 2011-05-26
      • 2013-12-09
      • 1970-01-01
      相关资源
      最近更新 更多