C# 使用LINQ to SQL读取大型表:内存不足与分页速度慢

C# 使用LINQ to SQL读取大型表:内存不足与分页速度慢,c#,sql,database,linq,skip-take,C#,Sql,Database,Linq,Skip Take,我有一个巨大的表格,我需要按照一定的顺序阅读,并计算一些汇总统计数据。该表已经有了正确顺序的聚集索引,因此获取记录本身非常快。我正在尝试使用LINQtoSQL来简化我需要编写的代码。问题是,我不想将所有对象加载到内存中,因为DataContext似乎让它们保持在内存中,但试图对它们进行分页会导致可怕的性能问题 这是故障。最初的尝试是: var logs = (from record in dataContext.someTable where [index is appro

我有一个巨大的表格,我需要按照一定的顺序阅读,并计算一些汇总统计数据。该表已经有了正确顺序的聚集索引,因此获取记录本身非常快。我正在尝试使用LINQtoSQL来简化我需要编写的代码。问题是,我不想将所有对象加载到内存中,因为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到SQL实体被保留在内存中,并且没有被正确地处理。因此,在阅读之后,我尝试了以下方法。这似乎是许多人使用的常见跳转/跳转模式,并增加了节省内存的功能

请注意_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;
    }
}
现在我有了一个期望的行为,即当我通过数据流传输时,内存使用量根本不会增加。然而,我有一个更糟糕的问题:每次跳过都会导致数据加载越来越慢,因为底层查询似乎实际上会导致服务器浏览所有以前页面的所有数据。运行查询时,每个页面的加载时间越来越长,我可以看出这正在变成一个二次操作。此问题出现在以下帖子中:


我似乎找不到用LINQ实现这一点的方法,LINQ允许我通过分页数据限制内存使用,但仍然可以在固定时间内加载每个页面。有没有办法做到这一点?我的直觉是,可能有某种方法可以告诉DataContext在上面的第一种方法中显式忘记对象,但我不知道如何做到这一点。

在疯狂地抓住一些救命稻草之后,我发现DataContext的ObjectTrackingEnabled=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执行多个并发读取时,您不会得到错误,因为已经有一个打开的DataReader与此命令关联,必须先关闭它。应用程序只是进入一个无限循环,CPU使用率为100%。我不确定这是一个C错误还是一个特性。

我有一个巨大的表,我需要按一定顺序通读,并计算一些汇总统计数据。-在TSQL中的服务器上执行此操作……这就是它擅长的!不,统计数据要比这复杂得多,并且不能用SQL查询进行计算。数据需要按照一定的顺序进行迭代,计算出的数据在时间上是正确的,等等。不,统计数据比这更复杂,并且不能用SQL查询进行计算——真的吗?可以给出一个完整的例子吗?我已经大大简化了在这里发布的代码。我正在按时间顺序遍历用户交互日志,并在每个时间点计算机器学习算法的一些特性。我正在计算诸如香农熵、运行min/max/avg/stdev的聚合、用户看到的项目的聚合值等,每个输入大约涉及50个总值。每次计算都涉及很多状态,还有非SQL函数,我非常确定通过流式传输数据而不是试图用SQL编写数据会更容易进行计算和更改。我遇到了完全相同的问题,读取了1m行,并且内存不足。当ObjectTrackingEnabled设置为false时,垃圾收集确实释放了此函数使用的内存。没有它,大约300Mb的内存无法释放。所以这个小小的环境可以产生巨大的影响。