【问题标题】:System.OutOfMemoryException thrown after reading 4000 files读取 4000 个文件后抛出 System.OutOfMemoryException
【发布时间】:2016-06-02 10:29:42
【问题描述】:

我们希望将 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,以及如何更快地做到这一点?

【问题讨论】:

  • 您的系统不会有无限的内存,您可能需要定期保存数据然后将其丢弃。或者甚至读取一个文件做你需要做的然后从内存中删除它。
  • 我该如何处理它?我怎样才能从内存中删除它
  • 那是在 32 位进程中运行的吗?此外,最好为 每个 插入创建新的实体框架上下文,并分别加载每个实体,不要重用上下文和实体。
  • 不知道DossierFichier 是什么我不知道。这取决于您为什么需要文件字节。如果您只是读取字节并将它们存储在某处,那么我的答案将不同于您是否需要所有内存来满足程序的长度。
  • 附带说明:读取 52k 文件需要一些时间。就这样。出于好奇,文件的平均大小是多少?

标签: c# entity-framework sql-server-2012


【解决方案1】:

看起来您的代码具有 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);

【讨论】:

  • 或者只是为每次更新创建新的上下文,通常也可以正常工作。
  • 您可能也应该记下将字节保留在内存中,即df.Fichier = File.ReadAllBytes(path); 即使有了您描述的更新,由于所有这些字节都在内存中,仍然有可能出现内存不足。
  • @TheLethalCoder 你是对的,我没有意识到内容保存在循环变量的Fichier字段中。感谢您的评论!
  • 我为每个文件添加了一个 DbContext 但我仍然有内存不足的异常
  • @amina 诀窍是确保当您不再需要之前的文件时,DbContexts 会被丢弃,并且一旦调用 context.SaveChanges() 成功返回,您就设置 df.Fichier = null
【解决方案2】:

对于集合中的每个项目,您将整个文件内容(字节数组)添加到df.Fichier。这是内存使用量增加的原因之一。

您可以通过使用临时变量来解决此特定问题。

【讨论】:

  • 创建字节后如何释放内存?
  • 我为每个文件添加了一个 DbContext 但我仍然有内存不足的异常
  • @Evk 我为每个文件添加了一个 DbContext 但我仍然有内存不足异常
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-07-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多