在读取了4000个文件后,抛出了System.OutOfMemoryException异常。

3
我们希望将52000个文件(.pdf.xls.doc等)导入到SQL Server 2012数据库中。
我有一个包含文件名的Dossier_fichier.txt文件。我将这些名称加载到集合中,并循环遍历该集合并尝试在PiecesJointes目录中查找这些文件,并将其转换为字节并使用此代码插入到数据库中:
var dossierFichiers = addOrUpdateHelper.ReadEntities<DossierFichier, DossierFichierMap>("dossier_fichier.txt").ToArray();
// (2) Parcourir toutes les instances de DossierFichier chargées + Lire le fichier référencé + Le charger dans la propriété Fichier
var dirPath = System.IO.Path.Combine(Environment.CurrentDirectory, "piecesJointes");
var nbfichier = 0;

foreach (var df in dossierFichiers) {
    try {
        var path = System.IO.Path.Combine(dirPath, string.Concat( df.Code,"_", df.Nom));
        df.Fichier = File.ReadAllBytes(path);
        context.DossierFichier.Add(df);
        context.SaveChanges();

        Logger.Info("Le fichier {0} a été inséré", df.Nom);

        nbfichier++;
    } catch (FileNotFoundException ex) {
        Logger.Error("Fichier {0} : {1}", df.Nom, ex.Message);
    } catch (Exception ex) {
        Logger.Error("Fichier {0} : {1}",df.Nom, ex.Message);
    }
}

在插入了4000个文件后,我遇到了OutOFMemoryException错误,并且花费了很长时间(60小时)。请问您如何在不出现OutOfmemoryException的情况下插入所有这些文件,并且加快速度?


1
您的系统不会拥有无限内存,您可能需要定期将数据保存并处理掉。甚至可以读取一个文件,完成所需操作后就将其从内存中删除。 - TheLethalCoder
这是否在32位进程中运行?此外,最好为每个插入操作创建新的Entity Framework上下文,并单独加载每个实体,不要重用上下文和实体。 - Evk
如果我不知道 DossierFichier 是什么,我就一点头绪都没有。它取决于你为什么需要这些文件字节。如果你只是读取字节并将它们存储在某个地方,我的答案将与你需要整个程序长度的内存不同。 - TheLethalCoder
2
顺便提一下:读取52k个文件需要一些时间。就像现在这样。出于好奇,这些文件的平均大小是多少? - Nahuel Ianni
你的 DbContext 可能缓存了很多你不再需要的东西。你需要偶尔释放并处理它。你还应该考虑 https://msdn.microsoft.com/en-gb/data/jj556205.aspx - spender
显示剩余4条评论
2个回答

5

看起来你的代码性能是O(n2),因为它将所有文件内容保存在内存中。

  • 循环的第一次迭代保存了你添加的第一个对象
  • 第二次迭代保存第二个对象并刷新第一个对象
  • 第三次迭代保存第三个对象并刷新前两个对象
  • ...以此类推
  • N次迭代插入第N个对象,并刷新先前插入的N-1个对象

你可以通过“批处理”更新来修复它:

  • 添加一个计数器,记录你已经插入了多少个对象
  • 不要立即调用context.SaveChanges();
  • 当插入的对象数量达到100时,调用context.SaveChanges();,然后用新实例替换context

这将确保所有对象仅保存一次,数据库往返次数受控制,并且您从未在内存中保存超过100个对象。

此外,你通过将其放入df.Fichier中来保留文件内容。这可能会使系统耗尽内存,所以你应该复制df

var path = System.IO.Path.Combine(dirPath, string.Concat( df.Code,"_", df.Nom));
var dfCopy = new DossierFichier(df); // Copy df's fields
dfCopy.Fichier = File.ReadAllBytes(path);
context.DossierFichier.Add(dfCopy);

或者每次更新都创建新的上下文,通常也可以正常工作。 - Evk
你应该注意保留字节在内存中的情况,例如:df.Fichier = File.ReadAllBytes(path); 即使根据你提供的更新仍有可能由于所有这些字节被存储在内存中而导致内存不足。 - TheLethalCoder
@TheLethalCoder 你说得对,我没有意识到内容保存在循环变量的“Fichier”字段中。感谢您的评论! - Sergey Kalinichenko
我为每个文件添加了一个DbContext,但仍然出现了内存不足异常。 - amina
1
@amina 的技巧在于确保在不再需要之前文件的 DbContext 时将其丢弃,并且在成功返回 context.SaveChanges() 调用后设置 df.Fichier = null - Sergey Kalinichenko
没问题,它运行良好,谢谢 @dasblinkenlight - amina

1
对于集合中的每个项目,您将整个文件内容(字节数组)添加到df.Fichier中。这是内存使用量增加的原因之一。
您可以通过使用临时变量来解决此特定问题。

创建字节后如何释放内存? - amina
我为每个文件添加了一个DbContext,但仍然出现了内存不足异常。 - amina
我为每个文件添加了一个DbContext,但仍然出现了内存不足异常。 - amina

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