一尘不染

使用LINQ to SQL读取巨大的表:内存不足与分页缓慢

sql

我有一个巨大的表,需要按一定顺序阅读并计算一些汇总统计信息。该表已经具有用于正确顺序的聚集索引,因此获取记录本身的速度非常快。我正在尝试使用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实体被保留在内存中,并且没有被正确处理。因此,在创建大量对象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在上面的第一种方法中显式地忽略该对象,但是我找不到如何做到这一点。


阅读 129

收藏
2021-03-17

共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.:应用程序进入CPU使用率为100%的无限循环。我不确定这是C#错误还是功能。

2021-03-17