使用LINQ处理大型数据集

时间:2009-11-10 02:21:56

标签: c# linq linq-to-sql c#-3.0

每次我使用LINQ to SQL编写下面表单的程序时,我最终会得到一个程序,它在运行时会抓取越来越多的内存,并且在可能只有25,000条记录之后消耗掉2GB的堆。我总是最终使用ADO.NET重写它。我做错了什么?

澄清:这个问题与加工速度无关;关于让它变得更快的答案是无关紧要的。

foreach (int i=0; i<some_big_number; i++)
{
    using (myDC dc = new myDC())  // my DataContext
    {
        myRecord record = (from r in dc.myTable where r.Code == i select r).Single();

        // do some LINQ queries using various tables from the data context
        // and the fields from this 'record'.  i carefully avoid referencing
        // any other data context than 'dc' in here because I want any cached
        // records to get disposed of when 'dc' gets disposed at the end of 
        // each iteration.

        record.someField = newValueJustCalculatedAbove;
        dc.SubmitChanges();
    }
}

4 个答案:

答案 0 :(得分:6)

您正在对数据上下文施加压力,以便每次都从头开始生成查询。

请尝试使用已编译的查询。

答案 1 :(得分:3)

每次循环迭代都要去数据库两次 - 一次检索行,然后再次更新行。这不是很有效。

您应该分批操作:

  • 通过选择范围而不是单个值来获取一组行,即第一批为0-100,下一批为101-200,依此类推。如果您在“代码”列上定义了聚簇索引,这将是最快的。

  • 在进入循环之前创建数据上下文

  • 在循环内部,只需更新对象

  • 调用SubmitChanges()循环结束后,这将在单个连接/事务中将所有更新发送到数据库

  • 重复下一批

您应该使批量大小可配置,因为您无法确定哪个批量大小将产生最佳性能 - 请勿将其硬编码到应用程序中。

另外,我会使用SingleOrDefault()和null检查而不是Single(),除非你可以保证i的任何值都会有一行。

编辑:

就内存使用而言,这更难控制,但它并不是LINQ to SQL所特有的,任何批处理算法都必须处理这个问题。虽然我不建议在实践中使用GC.Collect(),但通常在处理大批量后作为解决方法就足够了。

您还可以考虑减少每行检索的数据量(取决于开始时的数量)。您可以创建一个新实体,该实体映射到同一个表中的一组更小的列,可能只有一个或两个,这样当您选择该实体时,您只需要检索要开始使用的列。这将改善速度和内存占用,因为更少的数据通过线路传输,并且对象要小得多。

答案 2 :(得分:1)

我无法复制问题。内存使用量持平。性能缓慢,但记忆力不断。

你确定你没有在其他地方泄漏吗?您能否生成一个可以重现问题的最小代码示例?

编辑:

我使用了几乎相同的示例代码:

for (int ii = 1; ii < 200000; ii++)
{
    using (var dc = new PlayDataContext())
    {
        var record = 
            (from r in dc.T1s where r.Id == ii select r).SingleOrDefault();
        if (record != null)
        {
            record.Name = "S";
            dc.SubmitChanges();
        }
    }
}

没有问题。

所以要排除的事情:

  • 框架版本。我是最新的。
  • DataContext / entity复杂性。我的测试表只有两个字段,Id(int)和Name(nvarchar(max))。

您是否可以使用最新的FW重现问题,并使用一个小样本DataContext?

答案 3 :(得分:0)

您可以尝试在for循环之外创建datacontext,但不确定可以节省多少内存。

也许你可以在每次循环后调用GC.Collect(),看看你是否可以手动进行垃圾收集。