类型为'System.OutOfMemoryException'的异常被抛出。为什么?

16

我有一个动态查询,返回大约590,000条记录。第一次运行成功,但如果我再次运行它,我会不断收到System.OutOfMemoryException的错误提示。可能出现这种情况的一些原因是什么?

错误发生在这里:

  public static DataSet GetDataSet(string databaseName,string
                                   storedProcedureName,params object[] parameters)
    {
        //Creates blank dataset
        DataSet ds = null;

        try
        {
            //Creates database
            Database db = DatabaseFactory.CreateDatabase(databaseName);
            //Creates command to execute
            DbCommand dbCommand = db.GetStoredProcCommand(storedProcedureName);
            dbCommand.CommandTimeout = COMMAND_TIMEOUT;
            //Returns the list of SQL parameters associated with that stored proecdure
            db.DiscoverParameters(dbCommand);

            int i = 1;
            //Loop through the list of parameters and set the values
            foreach (object parameter in parameters)
            {
                dbCommand.Parameters[i++].Value = parameter;
            }
            //Retrieve dataset and set to ds
            ds = db.ExecuteDataSet(dbCommand);
        }
            //Check for exceptions
        catch (SqlException sqle)
        {
            throw sqle;
        }
        catch (Exception e)
        {
            throw e; // Error is thrown here.
        }
        //Returns dataset
        return ds;
    }

这是在按钮点击时运行的代码:

protected void btnSearchSBIDatabase_Click(object sender, EventArgs e)
{

        LicenseSearch ls = new LicenseSearch();

        DataTable dtSearchResults = new DataTable();

        dtSearchResults = ls.Search();

        Session["dtSearchResults"] = dtSearchResults;

        Response.Redirect("~/FCCSearch/SearchResults.aspx");
        }
        else
            lblResults.Visible = true;
    }

5
590K行有点过多,你觉得呢? - StingyJack
问题不就是你将 DataTable 存储在 Session 中,然后重新查询时,你同时在内存中有 Session 变量和原始 DataTable 吗?顺便说一下,不要调用 throw e;,应该调用 throw;,否则你的堆栈跟踪会说是 catch 处理程序生成了异常,但实际上是包含代码生成了异常。 - Dominic Zukiewicz
5个回答

42
第一次运行成功,但如果再次运行,我会不断收到System.OutOfMemoryException异常。这可能是什么原因?
无论其他人说什么,错误与忘记处理DBCommand或DBConnection没有关系。您也无法通过释放它们中的任何一个来修复错误。
错误完全与包含近60万行数据的数据集有关。显然,您的数据集消耗了机器可用内存的50%以上。在第一个数据集被垃圾回收之前返回相同大小的另一个数据集时,内存将用光。就是这样简单。
您可以通过以下几种方式解决此问题:
考虑返回更少的记录。我个人想象不出返回600K条记录什么时候对用户有用过。为了最小化返回的记录,请尝试:
- 将查询限制为前1000条记录。如果查询返回的结果超过1000个,请告知用户缩小搜索结果。 - 如果您的用户真的坚持一次看那么多数据,请尝试分页数据。请记住:Google从未一次显示所有22 bajillion搜索结果,它每次只显示20条左右的记录。Google可能没有将所有的22 bajillion结果保存在内存中,而是找到使用重新查询数据库生成新页面更节省内存的方法。
如果您只需迭代数据并且不需要随机访问,请尝试返回Datareader。 Datareader一次只加载一条记录到内存中。
如果以上都不可行,则需要使用以下方法之一强制.NET在调用您的方法之前释放数据集使用的内存:
- 删除所有对旧数据集的引用。任何持有数据集引用的内容都会阻止其被内存回收。
  • 如果您无法将数据集的所有引用置为空,请清除数据集中的所有行以及绑定到这些行的任何对象。这将删除对数据行的引用,使它们可以由垃圾收集器释放。

  • 我认为您不需要调用GC.Collect()来强制生成循环。不仅通常不建议调用GC.Collect(),因为足够的内存压力会导致.NET自动调用垃圾收集器。

    注意:在数据集上调用Dispose不会释放任何内存,也不会调用垃圾收集器,也不会删除对数据集的引用。Dispose用于清理非托管资源,但是DataSet没有任何非托管资源。它只是实现IDispoable,因为它从MarshalByValueComponent继承,所以数据集上的Dispose方法几乎没有用处。


    如果用户不选择任何额外条件并运行查询,则确切返回754600行。 我已经打开了分页,但所有结果仍然返回到数据集中。 - Xaisoft
    我遇到了这个问题,我发现每次用户想要查看新页面时重新查询数据库要高效得多。虽然需要更多的代码,但没有其他解决方法。使用此代码来帮助您在SQL中分页结果: http://www.davidhayden.com/blog/dave/archive/2005/12/30/2652.aspx - Juliet

    7
    也许您没有清除上一次运行的连接/结果类,这意味着它们仍然在内存中挂起。

    你可以使用各种内存分析工具,但这是我找到的最好的一个:http://memprofiler.com/ - Kieron
    在你刚刚发布的那个例子中,请确保命令和连接已经被关闭/处理。 - Kieron
    谢谢,我会尝试一下。我以前从未使用过内存分析器。 - Xaisoft
    你是在提到 DbCommand,对吧?那么我应该关闭和处理示例的哪一部分呢?我还在使用企业库,这会自动关闭连接吗? - Xaisoft
    使用 (DbCommand ...) { ... 做一些事情 ... 获取数据集 ... }正如 Quarrelsome 指出的那样,如果你使用 'using' 语句,它会自动调用 Dispose 方法。 - Kieron
    显示剩余2条评论

    3

    你显然没有正确处理资源的释放。

    当临时使用实现了IDisposable接口的对象时,请考虑使用"using"命令。


    1

    尽可能地尝试将您的大数据分解,因为我已经多次遇到这种问题。其中我有超过10万条记录和15个列。


    0

    它失败的地方在哪里?

    我同意你的问题可能是你的600,000行数据集太大了。我看到你正在将其添加到Session中。如果你使用Sql会话状态,它也必须对该数据进行序列化。

    即使你正确地处理了对象,如果你运行两次,每次在会话中和过程代码中至少都将有2个副本的数据集保存在内存中。这在Web应用程序中永远不会扩展。

    做一下数学计算,即使每行只有1-128位GUID,600,000行也将产生9.6兆字节(600k * 128 / 8)的数据,更不用说数据集的开销了。

    缩小你的结果。


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