DbContext 内存不足异常

4

我有一个包含超过20M条记录的DbContext,需要转换为不同的数据格式。因此,我将数据读入内存,执行一些任务,然后释放DbContext。代码可以正常运行,但是过一段时间后,我会收到OutOfMemoryExceptions错误。我已经能够缩小问题范围,发现以下代码片段:首先检索2M条记录,然后释放它们并再次获取。第一次检索工作正常,第二次检索会抛出异常。

// first call runs fine
using (var dbContext = new CustomDbContext())
{
    var list = dbContext.Items.Take(2000000).ToArray();
    foreach (var item in list)
    {
        // perform conversion tasks...
        item.Converted = true;
    }
}

// second call throws exception
using (var dbContext = new CustomDbContext())
{
    var list = dbContext.Items.Take(2000000).ToArray();
    foreach (var item in list)
    {
        // perform conversion tasks...
        item.Converted = true;
    }
}

GC难道不应该自动释放在第一个using语句块中分配的所有内存,使得第二个语句块的运行与第一个一样顺畅吗?

在我的实际代码中,我并不是一次检索200万条记录,而是在每次迭代中检索0到30K条左右。然而,在大约15分钟后,我会耗尽内存,尽管所有对象都应该已被释放。


另一个问题:如果您调用ToList()而不是ToArray(),是否有任何区别? - Jeff Prince
这是准确的代码吗?变量list应该像您发布的那样是本地的。此外,您使用的using块将自动处理DbContext的释放,而不是列表,如果list不像您在此处发布的那样是本地的,则可能会出现问题。 - Hopeless
@Shimmy,在第三次迭代之后也会发生同样的情况。 - christian
@Hopeless,是的,这就是确切的代码。 - christian
我找到了一个解决方案,但是我无法解释为什么它有效。将using块重构为一个方法并调用此方法后,内存得到释放(在任务管理器中进行了检查)。 - christian
显示剩余3条评论
4个回答

1

我没有察觉到涉及到大对象的任何提示。也许你应该先问问原帖作者。 - Gert Arnold
没有涉及到大型对象。实际上,这些对象非常小,只有几个整数和字符串。 - christian

0

IEnumerable有GetEnumerator(),所以如果你只想读取数据,可以尝试使用它来避免使用不必要的.ToArray()或.ToList():

// first call
using (var dbContext = new CustomDbContext())
{
    foreach (var item in dbContext.Items.Take(2000000))
    {
        // perform conversion tasks...
        item.Converted = true;
    }
}

// second call
using (var dbContext = new CustomDbContext())
{
    foreach (var item in dbContext.Items.Take(2000000))
    {
        // perform conversion tasks...
        item.Converted = true;
    }
}

不幸的是,这没有任何效果。 - christian

0

运行垃圾回收并不能帮助你,你必须在不同的上下文中运行每个迭代,并且释放你的上下文。

// ID is your primary key

long startID = 0;
while(true){
    using(var db = new CustomDbContext()){
        var slice = db.Items.Where(x=>x.ID > startID)
                            .OrderBy(x=>x.ID)
                            .Take(1000).ToList();

        // stop if there is nothing to process
        if(!slice.Any())
             break;

        foreach(var item in slice){
            // your logic...
            item.Converted = true;
        }

        startID = slice.Last().ID;
    }
}

如果你想更快地处理这些事情,另一种方法是并行运行切片...。 另一种方法 我建议将切片分成100x100,然后可以同时处理100个100项的切片。
您始终可以轻松地自定义切片以满足您的速度需求。
public IEnumerable<IEnumerable<T>> Slice(IEnumerable<T> src, int size){
     while(src.Any()){
         var s = src.Take(size);
         src = src.Skip(size);
         yield return s;
     }
}

long startID = 0;
while(true){
    using(var db = new CustomDbContext()){
        var src = db.Items.Where(x=>x.ID > startID)
                            .OrderBy(x=>x.ID)
                            .Take(10000).Select(x=>x.ID).ToList();

        // stop if there is nothing to process
        if(!src.Any())
             break;

        Parallel.ForEach(src.Slice(100), slice => {

             using(var sdb = new CustomDbContext()){
                 foreach(var item in sdb.Items.Where(x=> slice.Contains(x.ID)){
                     item.Converted = true;
                 }
             }

        } );

        startID = src.Last();
    }
}

0

重构后,内存得到释放。我不知道为什么,但它起作用了。

private static void Debug()
{
    var iteration = 0;
    while(true)
    {
        Console.WriteLine("Iteration {0}", iteration++);
        Convert();
    }
}

private static void Convert()
{
    using (var dbContext = new CustomDbContext(args[0]))
    {
        var list = dbContext.Items.Take(2000000).ToList();
        foreach (var item in list)
        {
            item.Converted = true;
        }
    }
}

当我将Convert()的内容移动到Debug()中的while循环中时,会抛出OutOfMemoryExceptions异常。
private static void Debug()
{
    var iteration = 0;
    while(true)
    {
        Console.WriteLine("Iteration {0}", iteration++);
        using (var dbContext = new CustomDbContext(args[0]))
        {
            // OutOfMemoryException in second iteration
            var list = dbContext.Items.Take(2000000).ToList(); 
            foreach (var item in list)
            {
                item.Converted = true;
            }
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接