【问题标题】:Read huge table with LINQ to SQL: Running out of memory vs slow paging使用 LINQ to SQL 读取大表:内存不足与分页缓慢
【发布时间】:2012-09-10 04:45:25
【问题描述】:

我有一个巨大的表格,我需要按特定顺序阅读并计算一些汇总统计信息。该表已经有一个正确顺序的聚集索引,因此获取记录本身非常快。我正在尝试使用 LINQ to SQL 来简化我需要编写的代码。问题是我不想将所有对象加载到内存中,因为 DataContext 似乎将它们保留在周围——但尝试对它们进行分页会导致可怕的性能问题。

这是细分。最初的尝试是这样的:

var logs = 
    (from record in dataContext.someTable 
     where [index is appropriate]
     select record);

foreach( linqEntity l in logs )
{
    // Do stuff with data from l
}

这是相当快的,而且流率很高,但问题是应用程序的内存使用量不断增加,而且永无止境。我的猜测是 LINQ to SQL 实体被保存在内存中并且没有被正确处理。所以在阅读Out of memory when creating a lot of objects C# 之后,我尝试了以下方法。这似乎是许多人使用的常见Skip/Take 范式,并增加了节省内存的功能。

请注意,_conn 是预先创建的,并且会为每个查询创建一个临时数据上下文,从而导致相关实体被垃圾回收。

int skipAmount = 0;
bool finished = false;

while (!finished)
{
    // Trick to allow for automatic garbage collection while iterating through the DB
    using (var tempDataContext = new MyDataContext(_conn) {CommandTimeout = 600})
    {               
        var query =
            (from record in tempDataContext.someTable
             where [index is appropriate]
             select record);

        List<workerLog> logs = query.Skip(skipAmount).Take(BatchSize).ToList();
        if (logs.Count == 0)
        {
            finished = true;
            continue;
        }

        foreach( linqEntity l in logs )
        {
            // Do stuff with data from l
        }

        skipAmount += logs.Count;
    }
}

现在,当我通过数据流式传输时,我的内存使用量根本不会增加。然而,我有一个更糟糕的问题:每个Skip 都导致数据加载越来越慢,因为底层查询似乎实际上导致服务器遍历所有先前页面的所有数据。在运行查询时,每个页面的加载时间越来越长,我可以看出这正在变成二次运算。此问题已出现在以下帖子中:

我似乎找不到使用 LINQ 执行此操作的方法,该方法允许我通过分页数据来限制内存使用,但仍然可以在恒定时间内加载每个页面。有没有办法正确地做到这一点? 我的直觉是,可能有某种方法可以告诉 DataContext 明确忘记上述第一种方法中的对象,但我不知道该怎么做。

【问题讨论】:

  • “我有一个巨大的表,我需要按特定顺序读取它并计算一些聚合统计信息。” - 在 TSQL 的服务器上执行它......这就是它的优点!
  • 不,统计数据比这更复杂,并且不能用 SQL 查询计算。数据需要按一定的顺序迭代,计算的东西在时间上是正确的,等等。
  • “不,统计数据比这更复杂,并且不能用 SQL 查询计算” - 真的吗?可以举个完整的例子吗?
  • 我已经大大简化了在这里发布的代码。我正在按时间顺序遍历用户交互日志,并在每个时间点为机器学习算法计算一些特征。我正在计算诸如香农熵之类的东西,运行 min/max/avg/stdev 的聚合,用户看到的项目的聚合值等,每个输入涉及大约 50 个总值。每个计算都涉及很多状态,还有非 SQL 函数,我非常确定通过流式传输数据而不是尝试用 SQL 编写数据来计算(和进行更改)会更容易。跨度>

标签: c# sql database linq skip-take


【解决方案1】:

在疯狂地抓了几根稻草之后,我发现DataContextObjectTrackingEnabled = false可能正是医生所要求的。毫不奇怪,它是专门为这种只读情况设计的。

using (var readOnlyDataContext = 
    new MyDataContext(_conn) {CommandTimeout = really_long, ObjectTrackingEnabled = false})
{                                                 
    var logs =
        (from record in readOnlyDataContext.someTable
         where [index is appropriate]
         select record);

    foreach( linqEntity l in logs )
    {
        // Do stuff with data from l   
    }                
}

通过对象进行流式传输时,上述方法不使用任何内存。写入数据时,我可以使用另一个启用了对象跟踪的DataContext,这似乎可以正常工作。但是,这种方法确实存在 SQL 查询可能需要一个小时或更长时间才能流式传输和完成的问题,因此如果有一种方法可以在不影响性能的情况下进行上述分页,我愿意接受其他选择。

关于关闭对象跟踪的警告:我发现当您尝试使用相同的 DataContext 进行多个并发读取时,您不会收到错误 There is already an open DataReader associated with this Command which must be closed first. 应用程序只是进入 100% CPU 使用率的无限循环。我不确定这是 C# 错误还是功能。

【讨论】:

  • 我遇到了完全相同的问题,读取 1m 行,内存不足。将“ObjectTrackingEnabled”设置为 false,垃圾收集确实 释放了此函数已使用的内存。没有它,大约 300Mb 的内存不会被释放。所以这个小设置可以产生巨大的影响。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-08-07
  • 1970-01-01
  • 1970-01-01
  • 2010-12-07
  • 1970-01-01
  • 1970-01-01
  • 2017-06-07
相关资源
最近更新 更多