18

SqlClient.SqlDataReader 是否是 .NET 托管对象?为什么我们必须调用 Close() 方法显式关闭打开的连接?这样一个对象的范围不应该自动关闭吗?垃圾收集器不应该清理它吗?

请帮助我了解这里的最佳做法是什么。

我在这里看到了一个相关的问题,它进一步说明了我在使用 Web 应用程序时遇到的问题。问题是我们的连接用完了。详细的错误在这里:

Exception: System.InvalidOperationException
Message: Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached.
Source: System.Data 

at  System.Data.SqlClient.SqlConnectionPoolManager.GetPooledConnection(SqlConnectionString options, Boolean& isInTransaction)
at  System.Data.SqlClient.SqlConnection.Open()

为了解决这个问题,我必须明确关闭所有 SQLDataReader 对象。

我正在使用 .NET Framework 3.5

4

8 回答 8

37

当然,它会在超出范围时被收集(如果它们没有其他引用)。当它被收集时,它将通过它的 Dispose() 方法关闭。然而,你永远不知道 GC 什么时候会释放东西;如果你不关闭你的读者,你很快就会用完与数据库的可用连接。

延伸阅读

~ 威廉·莱利-兰德

于 2008-10-30T15:02:00.080 回答
17

@弗罗斯特中尉

作为我们商店的一项规则,我们将所有数据库调用显式包装在 Try...Finally 块中,finally 部分捕获并关闭数据连接。为自己省去一个主要的故障排除头痛是值得的。

我有类似的规则,但我要求实现 IDisposable 的对象使用“使用”块。

using (SqlConnection conn = new SqlConnection(conStr))
{
     using (SqlCommand command = new SqlCommand())
     {
        // ETC
     } 
}

using 块在离开作用域时立即调用 Dispose,即使出现异常也是如此。

于 2008-10-30T15:13:18.200 回答
11

一种好的做法(只要您不重复使用连接)是将命令行为添加到 SqlDataReader 以在连接被释放时关闭连接:

SqlDataReader rdr = cmd.ExecuteReader( CommandBehavior.CloseConnection );

添加这将确保在关闭 SqlDataReader 对象(或垃圾收集)时关闭与数据库的连接。

但是,正如我之前所说,如果您计划在同一方法中将数据库连接重新用于另一个操作,则不希望这样做。

于 2008-10-30T15:18:20.080 回答
4

我想其他人都这么说,但我想清楚一点:

超出范围并不意味着立即进行垃圾收集。

您的应用程序需要在多个级别上“玩得很好”。关闭连接可以帮助您做到这一点。让我们来看看其中的几个级别。

1:您不依赖垃圾收集。理想情况下,垃圾收集不应该存在。但确实如此。但可以肯定的是,您不应该依赖它。

2:您没有保持数据库连接。虽然连接通常是池化的,但您发现存在限制。持有比必要时间更长的时间会使您的应用程序成为坏苹果。

3:您没有产生网络流量。每个数据库连接本质上都是一个 TCP 连接。保持打开状态可能会产生类似于“你还在那里吗?”的网络流量。是的。小流量是的,但在拥挤的网络上,这是不好的做法。SQL Server 本身正在使用资源来保持连接。其他人试图访问该 sql 服务器的资源可以更好地利用。

在考虑数据库访问时,您还必须考虑网络资源。某些获取数据的方法很糟糕,因为它们会带来不必要的东西。ADO 的早期版本因这类东西而臭名昭著。当您只需要数据时带回架构信息。当然,只有几个连接,这不是问题。但是从什么时候开始,任何数据库都只有几个连接。所以想想这些东西,尽量不要滥用资源。

当您的 Web 应用程序只有 100 个用户时,您可能不在乎。但是100,000呢?始终考虑扩展时会发生什么。

于 2008-10-30T18:22:00.833 回答
3

如果您没有明确关闭它,那么它会坐在那里等待垃圾收集器“收集”它......只有在这种情况发生之后,它才会被释放回 ADO.Net 连接池以供另一个 Connection.Open 重用请求,因此在所有介入的时间内,任何其他连接请求都必须创建一个全新的请求,即使有一个非常好的未使用的可以使用...

根据 GC 运行之前的时间以及正在执行的数据库请求的数量,您可能会用完与数据库的可用连接。

但是Command.ExecuteReader() 方法上有一个可选参数,称为CommandBehavior。这是一个枚举,其值为:CommandBehavior.Default、CommandBehavior.SingleResult、CommandBehavior.SchemaOnly、CommandBehavior.KeyInfo、CommandBehavior.SingleRow、CommandBehavior.SequentialAccess 和 CommandBehavior.CloseConnection

此枚举有一个 FlagsAttribute 属性,该属性允许按位组合其成员值。这是与此处相关的最后一个值 (CommandBehavior.CloseConnection)。它告诉 Command 对象在数据读取器关闭时关闭连接。 http://msdn.microsoft.com/en-us/library/system.data.commandbehavior.aspx

不幸的是,默认值不是在数据读取器关闭时关闭连接,但是当您希望您的方法在完成后立即将连接释放回池时,您可以(并且应该)将此参数作为 CommandBehavior.CloseConnection 传递它...

于 2008-10-30T17:41:50.787 回答
2

术语“托管代码”所指的“托管”资源是内存。就是这样。任何其他稀缺资源都需要使用一次性模式进行包装,包括数据库连接。

这对您来说是个问题的原因是垃圾收集器在超出范围时不会为每个对象运行。不那么频繁地收集更多物品会更有效率。因此,如果您等待收集器处理您的对象(是的,如果您最终实现了 idisposable ),您可能会保持许多数据库连接打开的时间比您意识到的要长得多。

于 2008-10-30T15:05:05.690 回答
2

问题不是连接,而是SqlDataReader 持有的SQL 游标。如果您尝试打开第二个而不关闭第一个,它将引发异常。

于 2008-10-30T15:15:53.313 回答
1

还要考虑抛出异常时会发生什么 - 如果您突然被迫退出正在执行的代码,您永远不知道连接是否会关闭。

作为我们商店的一项规则,我们将所有数据库调用显式包装在 Try...Finally 块中,finally 部分捕获并关闭数据连接。为自己省去一个主要的故障排除头痛是值得的。

于 2008-10-30T15:09:28.177 回答