【问题标题】:SqlCommand.Dispose() not disposing the SqlParameters in it - Memory Leak - C#.NETSqlCommand.Dispose() 未在其中处理 SqlParameters - 内存泄漏 - C#.NET
【发布时间】:2011-03-01 20:17:35
【问题描述】:

我有一个以 MS SQL Server 2005 作为后端的 Windows 窗体应用程序。我已经在表单中编写了代码来使用 SqlConnection、SqlCommand 对象调用一些存储过程,并且我正确地处理了所有内容。

我已经通过调用处理了 sqlcommand 对象

oSqlCommand.Dispose()

但我目睹了我的应用程序消耗了大量内存。我基本上将大型 XML 文件作为 SqlParameters 传递。

我最终决定使用 RedGate 内存分析器对其进行内存分析,我注意到 System.Data.SqlClient.SqlParameters 没有被释放。

对此有何见解?

谢谢

NLV

【问题讨论】:

    标签: c# memory-leaks sqlcommand


    【解决方案1】:

    Dispose 不释放它的参数,只释放它的内部 SqlMetaData 缓存...顺便说一句,参数不会自动释放是正常的,因为您可以在释放命令后传入不应该释放的东西... + SqlParameter也没有实现 Dispose,因为它没有非托管资源 ....

    【讨论】:

    • 那么如何配置SqlParameters呢?
    • 你为什么要这样做?通常你只为非托管资源实现 dispose ......你如何传递那些大的 xml 文件,作为字符串或 XmlReader 的?
    • 那么我不知道为什么正确处理了 sql 参数。我没有使用“使用”语句。相反,我明确地调用了 dispose。有什么问题吗?
    • 是的,如果可能,请使用 using 关键字而不是自己调用 dispose,using 会在幕后为您创建一个不错的 try{}finally{} 代码片段...您如何传递那些大 xml文件,作为字符串或 XmlReader 的,如果第二个适合你,那么你将不得不自己处理它......
    • 是的,我使用 new XmlNodeReader() 传递 xmls 并正确处理它(在我发现它也导致泄漏之后!:))。
    【解决方案2】:

    我看到了:

    我妥善处理了一切。

    还有这个:

    我已经通过调用 oSqlCommand.Dispose() 来处理 sqlcommand 对象

    但是,它们是互斥的!如果你直接调用.Dispose(),你就错了。具体来说,您保留打开异常可能使程序跳过对Dispose() 方法的调用的可能性。处理命令的“正确”方式使用 using 块创建它,如下所示:

    using (SqlCommand cmd = new SqlCommand("sql string here"))
    {
        // use the command here
    } // compiler transforms your code to make sure .Dispose() is called here
    

    现在,我从问题中得出结论,这不是目前的主要问题,但值得开车回家。

    关于参数的问题:SqlParameters 没有实现 IDisposable。因此,您不要直接处置它们。它们是完全托管的资源,这意味着它们在不再可访问后的某个时间点被垃圾收集器清除。您无需自行清理它们。

    如果您可以认真地证明 SqlParameter 对象在它们应该存在很长时间之后仍然存在,这意味着您在某处持有对它们的引用。例如,也许您正在某处“缓存”旧的 SqlCommand 对象,而这些对象又会保留它们的所有参数。不要那样做。查找并消除仍然引用 SqlParameters 的任何内容,垃圾收集器将为您清理它们。

    更新:

    重新阅读您的问题后,听起来 xml 参数最终出现在大对象堆上。 .Net 中的垃圾收集器是分代的——它不会在每次运行时都清理所有内容。当一个对象移动到更高的一代时,它更有可能停留一段时间。大对象堆基本上是最后一代,根本没有清理多少。更重要的是,它永远不会被压缩,以至于随着时间的推移它会分裂。这可能会导致程序保留比它需要的更多的数据。您需要做的是尝试找到一种方法来避免将参数的整个 xml 数据加载到内存中,这样它就永远不会进入大对象堆。改用文件流或类似的东西。

    【讨论】:

    • 我正在尝试将命令对象包装在 using 语句中。让我在几分钟内更新你。
    • 是的,我读过乔尔。我有一个包含多个表的庞大数据集,我需要将其作为历史记录保存在数据库中。所以我把它分解成多个 xml 文件并将其保存在多个 xml 列中。
    【解决方案3】:

    如果不对此进行测试,我可以想到两件事可能会对您有所帮助。使用SqlParameters,您可以使用finalize() 方法来释放资源。另外,您是否通过 using 块运行所有 Sql 命令?如果是这样,当 using 块完成时,您的资源应该被回收,它将消除您的内存泄漏问题。

    【讨论】:

      【解决方案4】:

      由于SqlParameter 不是IDisposable,所以处理它不是问题; 通常整理引用等没什么好处,因为它仍然受制于相同的 GC。

      如果听起来您不小心保留了对SqlCommand 的引用。但如果您确定已完成,您可以尝试将每个.Value 显式设置为null,并在参数列表中调用Clear()。但这实际上只是掩盖你坚持死命令的事实。

      【讨论】:

        【解决方案5】:

        我用过这个模式是几个项目都没有问题

        public partial class StoredProcedures
        {
            [SqlProcedure()]
            public static void InsertCurrency_CS(
                SqlString currencyCode, SqlString name)
            {
                using (SqlConnection conn = new SqlConnection("context connection=true"))
                {
                    SqlCommand InsertCurrencyCommand = new SqlCommand();
                    SqlParameter currencyCodeParam = new SqlParameter("@CurrencyCode", SqlDbType.NVarChar);
                    SqlParameter nameParam = new SqlParameter("@Name", SqlDbType.NVarChar);
        
        
        
                    InsertCurrencyCommand.CommandText =
                        "INSERT Sales.Currency (CurrencyCode, Name, ModifiedDate)" +
                        " VALUES(@CurrencyCode, @Name)";
        
                    InsertCurrencyCommand.Connection = conn;
        
                    conn.Open();
                    InsertCurrencyCommand.ExecuteNonQuery();
                    conn.Close();
                }
            }
        }
        

        参考:http://msdn.microsoft.com/en-us/library/5czye81z%28VS.80%29.aspx

        【讨论】:

          【解决方案6】:

          如果您确定保留最后一个引用的是 SqlParameter,您可以做几件事。

          首先,尝试将您的 XML 作为字符串传递(并在存储过程中使用 OPENXML 来处理它),看看是否有一个简单的对象和更多的控制会有所帮助。

          其次,制作自己的 SqlParameter-s,将它们保存在字典中,然后执行以下操作:

          foreach (SqlParameter param in parameters.Values)
              command.Parameters.Add(param);
          

          然后在您完成命令运行并处理命令并关闭(如果仍然打开)并处理连接后,进入您的字典,将 null 显式分配为 SqlParameter.Value (或者,将 .Value 中的字符串 ref 放入local var,将 String.Empty 分配给 .Value,然后将 null 分配给 local var——这仅适用于 SqlParameter.Value 抱怨直接 null。然后将 null 分配给字典项(这是 SqlParameter 的引用),然后分配 null到字典。

          在更简单的情况下,您可以只为那个关键的 SqlParameter 保留一个 ref 并跳过字典。 关键是要保持显式分配空值 - 字符串的最后一个引用,然后是包含它的 SqlParameter 的最后一个引用。

          请记住,这涉及到几件事。它从根本不解析中间层的 XML 开始 - 只是将其发送到 SQL 并以显式取消引用结束。如果您的代码实际上是在动态构建 XML,请将其设为一个大的直字符串来尝试。

          如果仅此一项并不能降低内存压力,那么您将不得不强制执行显式 GC 收集,但为此您必须做一些阅读并计划合理的时间间隔,因为 GC 成本很高,即如果您启动 GC-in在每次请求之后,你都会像疯兔子一样付出很多 CPU 周期的代价。

          此外,由于您没有说明您的数据实际有多大,以及您运行的是哪种硬件,如果您的中间层在 IIS 下运行,则很难推测可能的进一步选择,例如让 IIS 运行多个工作进程并且在它们变得过于臃肿时回收它们。对于真正巨大的内存消耗和真正的直通中间层(意味着没有缓存堆积),这可能比摆弄 gc 更快,但我们正在谈论非常大的数据才能进入该区域。

          【讨论】:

            猜你喜欢
            • 2022-12-06
            • 2010-11-13
            • 1970-01-01
            • 1970-01-01
            • 2015-06-14
            • 1970-01-01
            • 2011-03-22
            • 1970-01-01
            相关资源
            最近更新 更多