SqlConnection 是否该关闭?

有些时候阅读评论可能会比文章本身更有价值。


今天在查资料的时候,在博客园上看到了两篇文章《为什么要关闭数据库连接,可以不关闭吗?》和《正确使用SqlConnection对象,兼谈数据库连接池》,这两篇文章都引起了大家的讨论,我也是看得津津有味。

首先来说第一篇文章,在文中作者试图表示:“当同时使用数据库的人很少的时候,这个时候当打开一个数据库连接后,可以不用去 close。因为 close 之后再 open 会很耗时。” 且不论这个说法是否正确,但作者的代码着实让人捉急,首先作者在多线程的环境下使用 SqlConnection 对象,而后发现执行过程中 SqlConnection 被莫名的关闭了,就使用 lock 强制代码同步执行。

Parallel.For(1, Int32.MaxValue, (id) =>            
{    
      ExecuteCommand(conn, id);            
});

private static object syncObj = new object();
private static void ExecuteCommand(SqlConnection conn, int id)
{
    lock (syncObj)    
    {     
        if (conn.State != System.Data.ConnectionState.Open) 
              conn.Open();    
         Console.WriteLine("正在执行.." + id);        
         Thread.Sleep(100);        
         SqlCommand cmd = new SqlCommand(string.Format("Insert into Nums values('{0}') ", id), conn);        
         cmd.ExecuteNonQuery();    
     }
}


刚看完上一段的朋友肯定会很好奇如果觉得 close 再 open 要耗时间,那为什么不使用连接池呢? 要不是后来作者在评论中指出上述代码只是为了用C#说明问题才写的,实际上是在用vba执行 close 再 open 的操作时消耗了很多时间。不然真要怀疑楼主是否停药了。

LoveJenny:@金色海洋(jyk)阳光男孩

在ifix下用vba连接sqlserver,open-close很耗性能,所以对方公司出了这么个主意。

但通过后面的评论可以知道楼主的这种做法(不 close)在某些时候并没有错误。

如果是

open()

close()

对,那么各位可以在sql的"事件探测器"里追踪下open和close都做了些什么.


长连接技术,之所以节约开销,其中很重要的就是数据库不用执行sp_resetconnection()操作.这节约了不少的开销.特别是超大型程序和负载,一般我们是启动大型程序的时候,直接开N个线程到数据库服务器,其中N为数据库能接受的最大连接数,一般是上万.然后保持长连接和心跳.

sp_resetconnection()改为周期执行(一天或者几天,类似GC的操作).这样一来,可以最大化DB的CPU利用率,而节约了IO的吞吐开销.

楼主的办法,是很正确的做法.

之所以在“某些时候”加粗体,是因为后面就有评论表示这个 sp_resetconnection 方法在有些版本的数据库中已经不见了。


而关于多线程下访问 SqlConnection,也有园友给出了评论:

题外话:多线程环境,比如CS架构,数据库操作是必须要做锁的,而且一般是Observer模式冒泡到主线程,然后数据库操作是封装到singleton模块中的...这是最大众化的设计方式..

其它评论不再赘述,如果大家感兴趣,请直接查看原文。


接着,再来看第二篇。在该文中,作者说明了如何进行线程安全的调用(及时关闭在一个线程中打开的链接),并且说明了数据库连接池的优点。

在该文的评论中,大家重点讨论了 close 和 Dispose 在关闭数据库的问题上是否拥有相同的能力,以及并发数超过数据库连接数时可能会发生的事情。

下面,先来看一段作者引用 MSDN 的话。

接池使新连接必须打开的次数得以减少。池进程保持物理连接的所有权。通过为每个给定的连接配置保留一组活动连接来管理连接。每当用户在连接上调用 Open 时,池进程就会查找池中可用的连接。如果某个池连接可用,会将该连接返回给调用者,而不是打开新连接。应用程序在该连接上调用 Close 时,池进程会将连接返回到活动连接池集中,而不是关闭连接。连接返回到池中之后,即可在下一个 Open 调用中重复使用。 

只有配置相同的连接可以建立池连接。ADO.NET 同时保留多个池,每种配置各一个。在使用集成的安全性时,连接按照连接字符串以及 Windows 标识分到多个池中。还根据连接是否已在事务中登记来建立池连接。 

池连接可以显著提高应用程序的性能和可缩放性。默认情况下,在 ADO.NET 中启用连接池。除非显式禁用,否则,在应用程序中打开和关闭连接时,池进程会对连接进行优化。还可以提供几个连接字符串修饰符来控制连接池的行为。

--- MSDN

再来看一下,园友的评论。

SqlConnection内部有个成员变量TdsInternalConnection,该InternalConnection负责和Sql Server的socket通讯(使用TDS协议).SqlConnection的Close还是Disppose都只是将TdsInternalConnection返回给DbConnectionPool. 

TdsInternalConnection的Dispose才会断开与Sql Server的socket连接.DbConnectionPool根据一定的策略,比如空闲连接数超过最小连接数,则把多余的tds连接放入等候销毁的队列,连接池清理过程大概每隔2-3分钟就进行一次。 

SqlConnection的Open表示从DbConnectionPool中去获取一个TdsInternalConnection对象;Close(Dispose)则表示断开与TdsInternalConnection的引用,同时从TdsInternalConnection的引用列表中删除对SqlConnection的引用。 

从这种模式上来说,可以在同一个TdsInternalConnection上返回多个结果集(也就是执行多个Command),这也是为了支持事务特性。

可见,Close 和 Dispose 都只是把连接返回到连接池而已,连接池会每隔一段时间清理下多余的空闲连接从而节约资源。


而当并发数超过连接数的时候,应用程序就会报错,但是按园友的意思,300的连接数一般是够用了。

第301个连接请求是什么情况? 当然是失败了。 

不过一般也很难达到300,因为IIS的并发数也不高。 

听说博客园的IIS并发也才2000到3000多。这是所有的访问,包括图片、css、js等。asp.net除了访问数据库,还有很多事情要处理。 

所以想让连接池超过300 也不是很容易的事情。

--- 金色海洋(jyk)阳光男孩


结 论

关于数据库连接是否该及时关闭的话题其实已经很明了了,好的习惯还是使用完就关闭,而且我们调用 close 或 dispose 方法其实并不会真正关闭与数据库的 socket 通信,而只是把 “连接” 放回到数据库的连接池(由数据库维护)中,当下次使用相同的连接字符串和相同的 Windows 标识的时候,就会去池中取出一条空闲的连接。

SqlConnection 不是线程安全的,所以在多线程环境下访问 SqlConnection 建议使用单例模式。


文章索引

[隐 藏]

本站采用知识共享署名 3.0 中国大陆许可协议进行许可。 ©2014 Charlie Box | About Site | 浙ICP备13014059号