Command.Prepare()导致内存泄漏?

时间:2010-08-20 21:51:51

标签: c# database postgresql ado.net memory-leaks

我在这个科学建模项目中继承了一些代码,我和我的同事都被这个问题困扰了。写这篇文章的人现在已经不见了,所以我们不能问他(去图)。

在数据访问层内,有insert()方法。这听起来像 - 它将记录插入数据库。它被建模的各种对象使用,以在模拟过程中告诉数据库他们自己。

但是,我们注意到在经过大量数据库插入后的较长时间模拟中,我们最终得到了连接超时。所以我们提高了超时限制,然后我们开始从PostgreSQL获得“内存不足”错误。我们最终将问题确定为IDbCommand对象使用Prepare()的行。离开它会导致内存使用无限期地上升。注释掉这一行会导致代码工作正常,并消除所有内存问题。 Prepare()导致这种情况的原因是什么?我在文档中找不到任何解释这一点的内容。

代码的压缩版本如下。

public virtual void insert(DomainObjects.EntityObject obj)
{
    lock (DataBaseProvider.DataBase.Connection)
    {
        IDbCommand cmd = null; 
        IDataReader noInsertIdReader = null;
        IDataReader reader= null;

        try
        { 
            if (DataBaseProvider.DataBase.Validate)
            {    ...    }

            // create and prepare the insert command
            cmd = createQuery(".toInsert", obj);
            cmd.Prepare();     // This is what is screwing things up

            // get the query to retreive the sequence number
            SqlStatement lastInsertIdSql = DAOLayer...getStatement(this.GetType().ToString() + ".toGetLastInsertId");

            // if the obj insert does not use a sequence, execute the insert command and return
            if (lastInsertIdSql == null)
            {
                noInsertIdReader = cmd.ExecuteReader();
                noInsertIdReader.Close();
                return;
            }

            // append the sequence query to the end of the insert statement
            cmd.CommandText += ";" + lastInsertIdSql.Statement;

            reader = cmd.ExecuteReader();

            // read the sequence number and set the objects id
            ...
        }

        // deal with some specific exceptions
        ...
    }
}

编辑:(响应第一个给出的答案)所有数据库对象都放在finally块中。我只是为了节省空间而把这部分切掉了。我们已经玩了一点,并没有任何区别,所以我认为这不是问题。

2 个答案:

答案 0 :(得分:3)

您会注意到IDbCommand和IDataReader都实现了IDisposable。每当您创建IDisposable对象的实例时,您应该将其包装在using statement中,或者在完成后调用Dispose。如果不这样做,你最终会泄漏资源(有时候资源不仅仅是内存)。

在您的代码中尝试此操作

using (IDbCommand cmd = createQuery(".toInsert", obj))
{
    cmd.Prepare();     // This is what is screwing things up
    ...
    //the rest of your example code
    ...
}

编辑专门讨论准备

我可以从代码中看到您正在准备命令,然后再也不会重复使用它。

准备命令背后的想法是,它需要额外的开销来准备,但是每次使用命令时它都会比非预备语句更有效。如果你有一个你要重复使用的命令,这是很好的,并且是否是开销是否值得命令的性能提升。

所以在你向我们展示的代码中,你正在准备命令(支付所有开销)并且没有任何好处,因为你会立即抛弃命令!

我会回收准备好的命令,或者只是将调用放弃到prepare语句。

我不知道为什么准备好的命令会泄漏,但你不应该首先准备这么多的命令(特别是单用命令)。

答案 1 :(得分:2)

Prepare()方法旨在使查询更有效地运行。实现这一点完全取决于提供者。典型的创建临时存储过程,使服务器有机会预先解析和优化查询。

有几种方法可能会泄漏内存。一个是典型的.NET细节,IDbCommand类的实际实现总是有一个Dispose()方法,在终结器线程执行之前显式释放资源。我没有看到它在您的代码段中使用过。但在这种情况下,不太可能在没有运行垃圾收集器的情况下消耗所有内存。您可以从Perfmon.exe中了解并观察垃圾收集器的性能计数器。

下一个候选人更阴险,你正在使用 big 大块本机代码。 Dbase提供商并不那么简单。 FOSS类型往往旨在让您从中获取错误。源代码可用有原因。 Perfmon.exe再次诊断出来,看到托管堆没有增长到超出界限但私有字节爆炸是一个死的赠品。

如果您不想调试提供程序,则只需对语句进行评论即可。