ADO.Net连接池和连接字符串剖析
文章内容来源于网络
随着.Net的推出。数据库读取技术也由原本的ADO进化为ADO.Net。正如大家所知道的,ADO.Net较ADO提供了更便捷的数据库读写能力以及优秀的性能。
Connection Pool是ADO.Net一个用来提高性能的重要功能。但是对于Connection Pool的机制却很少有文档涉及,所以对于Connection Pool的排错,一直都是一个很棘手的问题。
对于OLEDB以及ODBC, 连接池是由Driver决定的。
对于Oracle的数据库,一般而言,8.0以上的版本都建议使用Oracle提供的ODP.Net。
所以此文主要探讨一下System.Data,SqlClient的Connection Pool。
1. Connection Pool 是什么呢 ?
每当程序需要读写数据库的时候。Connection.Open()会使用ConnectionString连接到数据库,数据库会为程序建立一个连接,并且保持打开状态,此后程序就可以使用T-SQL语句来查询/更新数据库。当执行到Connection.Close()后,数据库就会关闭当前的连接。很好,一切看上去都是如此有条不紊。
但是如果我的程序需要不定时的打开和关闭连接,(比如说 ASP.Net 或是 Web Service ),例如当Http Request发送到服务器的时候、,我们需要打开Connection 然后使用Select* from Table 返回一个DataTable/DataSet给客户端/浏览器,然后关闭当前的Connection。那每次都Open/Close Connection 如此的频繁操作对于整个系统无疑就成了一种浪费。
ADO.Net Team就给出了一个比较好地解决方法。将先前的Connection保存起来,当下一次需要打开连接的时候就将先前的Connection 交给下一个连接。这就是Connection Pool。
2. Connection Pool 如何工作的?
首先当一个程序执行Connection.open()时候,ADO.net就需要判断,此连接是否支持Connection Pool (Pooling 默认为True),如果指定为False, ADO.net就与数据库之间创建一个连接(为了避免混淆,所有数据库中的连接,都使用”连接”描述),然后返回给程序。如果指定为True,ADO.net就会根据ConnectString创建一个Connection Pool,然后向Connection Pool中填充Connection(所有.net程序中的连接,都使用”Connection”描述)。填充多少个Connection由Min Pool Size (默认为0)属性来决定。例如如果指定为5,则ADO.net会一次与SQL数据库之间打开5个连接,然后将4个Connection,保存在Connection Pool中,1个Connection返回给程序。
当程序执行到Connection.close() 的时候。如果Pooling 为True,ADO.net 就把当前的Connection放到Connection Pool并且保持与数据库之间的连接。同时还会判断Connection Lifetime(默认为0)属性,0代表无限大,如果Connection存在的时间超过了Connection LifeTime,ADO.net就会关闭的Connection同时断开与数据库的连接,而不是重新保存到Connection Pool中。(这个设置主要用于群集的SQL 数据库中,达到负载平衡的目的)。如果Pooling指定为False,则直接断开与数据库之间的连接。
然后当下一次Connection.Open() 执行的时候,ADO.Net就会判断新的ConnectionString与之前保存在Connection Pool中的Connection的connectionString是否一致。(ADO.Net会将ConnectionString转成二进制流,所以也就是说,新的ConnectionString与保存在Connection Pool中的Connection的ConnectionString必须完全一致,即使多加了一个空格,或是修改了Connection String中某些属性的次序都会让ADO.Net认为这是一个新的连接,而从新创建一个新的连接。所以如果您使用的UserID,Password的认证方式,修改了Password也会导致一个Connection,如果使用的是SQL的集成认证,就需要保存两个连接使用的是同一个)。然后ADO.net需要判断当前的Connection Pool中是否有可以使用的Connection(没有被其他程序所占用),如果没有的话,ADO.net就需要判断ConnectionString设置的Max Pool Size (默认为100),如果Connection Pool中的所有Connection没有达到Max Pool Size,ADO.net则会再次连接数据库,创建一个连接,然后将Connection返回给程序。如果已经达到了MaxPoolSize,ADO.net就不会再次创建任何新的连接,而是等待Connection Pool中被其他程序所占用的Connection释放,这个等待时间受SqlConnection.ConnectionTimeout(默认是15秒)限制,也就是说如果时间超过了15秒,SqlConnection就会抛出超时错误(所以有时候如果SqlConnection.open()方法抛出超时错误,一个可能的原因就是没有及时将之前的Connnection关闭,同时Connection Pool数量达到了MaxPoolSize。)如果有可用的Connection,从Connection Pool 取出的Connection也不是直接就返回给程序,ADO.net还需要检查ConnectionString的ConnectionReset属性(默认为True)是否需要对Connection 最一次reset。这是由于,之前从程序中返回的Connection可能已经被修改过,比如说使用SqlConnection.ChangeDatabase method 修改当前的连接,此时返回的Connection可能就已经不是连接当前的Connection String指定的Initial Catalog数据库了。所以需要reset一次当前的连接。但是由于所有的额外检查都会增大ADO.net Connection Pool 对系统的开销。
3. Connection Pool 如何设置呢?
要修改Connection Pool 唯一的方式就是通过设定Connection String来完成。
Pooling (true)
When true, the connection is drawn from the appropriate pool, or if necessary, created and added to the appropriate pool.
此属性代表是否需要使用到连接池,默认为True,如果指定为False,不使用连接池。
Connection Lifetime (0)
When a connection is returned to the pool, its creation time is compared with the current time, and the connection is destroyed if that time span (in seconds) exceeds the value specified by Connection Lifetime. This is useful in clustered configurations to force load balancing between a running server and a server just brought online.
A value of zero (0) will cause pooled connections to have the maximum time-out.
这个属性表示一个Connection的有效时间,如果一个Connection返回到ConnectionPool的时候,超过了Connection LifeTime时间,这个连接不会再次放到Connection。当下一个请求发来时,ADO.Net会新建一个Connection。
这个属性主要使用于群集的SQL数据库中,用于负载平衡。
Enlist (True)
When true, the pooler automatically enlists the connection in the current transaction context of the creation thread if a transaction context exists.
Max Pool Size (100)
The maximum number of connections allowed in the pool.
Min Pool Size (0)
The minimum number of connections maintained in the pool.
ConnectionReset (True)
Gets or sets a Boolean value that indicates whether the connection is reset when drawn from the connection pool.
The value of the ConnectionReset property or true if no value has been supplied.
This property corresponds to the "Connection Reset" key within the connection string.
当Connection从Connection Pool 中取回的时候,为了保证新的Connection 不会因为前一次
附带的提一下,在ADO.net 2.0 的世界中,修改SqlConnectionString 我们可以使用SqlConnectionStringBuilder类来完成
Ø Connection.dispose() vs Connection.close()
可能大家经常看到网络上有很多文档以及MSDN站点都推荐大家使用using(sqlconnection cn=new sqlconnection()){}这样的方式来创建Connection,因为当超过{}后,.net framwork会自动执行Connection.dispose()方法,所以能够确保Connetion被及时的关闭。
1)那么及时的调用.dispose()真的这么重要么,如果一个对象超出了生存空间,在.net中不是会自动被GC(垃圾回收器)自动清理的么?
这个问题其实是由于GC导致的,.net中使用的GC,他对于工作并不像我们这样勤奋。GC只有当外界环境极其恶劣的时候(没有足够的内容分配的时候)他才会动手打扫卫生(清理不使用的对象)。所以对于Connection 即使超出了变量的生命周期,它可能还没有被GC干掉。依旧未将Connection返回给Connection Pool。
所以这就导致了下一个连接可能会有Connection Pool中没有Available的Connection而从新打开一个新的连接,无端的浪费了多余的性能。所以ADO.net team反复强调要及时的关闭当前的连接。一个最好的方法就是使用using{}block 系统会在退出{}的时候自动调用connection.dispose方法,而dispose会自动去执行close方法,释放当前的connection。
2)Dispose 究竟做了些什么?
protected override void Dispose(bool disposing)
...{
if (disposing)
...{
this._userConnectionOptions = null;
this._poolGroup = null;
this.Close();
}
this.DisposeMe(disposing);
base.Dispose(disposing);
}
其实Connection.dispose方法就是call了一次close方法,所以两者是等同的。也就是说,如果您及时的执行了connection.close()方法,就没有必要必须再把connection包裹在一个using(){}中。
3)如果使用using 是必需的,那么如果程序结构导致我无法使用using(){}来包裹我的Connection,比如说我的Connection是同一个help类返回的,那我又怎么办呢?
这是一个经常遇到的问题。在这样的环境中,我们无法将整个connection包裹在一个connection中。
解决这样的方法有两个,一个就是修改您的代码结构。传入一个ConnectionString来返回Connection。另一个方法就是反复检查您的代码,是否及时关闭了Connection。因为Close的效果与dispose是相同的。但是如果不使用using(){}这个及时关闭Connection的任务就等于是交到了我们自己的手上,而不再由.net framework为我们把关了。
Ø 说了这么多,那么我们什么时候需要使用到Connection Pool呢?
一般而言这应该由您的项目需求而决定。
如果您的项目是ASP.net/WebService 我们会建议您使用Connection Pool因为这个功能能够帮助您减少由于频繁创建连接带来的巨大系统开销。
如果您的系统是一个C/S模型结构,我们会不建议您使用Connection Pool,这是由于一般而言,在C/S这样的模型中,每一个用户都是使用自己的用户名密码去连接后台数据库,使用的都是不同的Connection String,基本不会出现频繁出现打开/关闭数据库连接的问题,实际上在C/S模型中,您可以一直使一个Connection保持open的关闭,而不Close,这样更能够提高您系统的性能,不会由于Connection Pool的额外检查而带来系统资源的消耗,同时也不必担心一直打开的Connection长时间的占用了连接,导致其他的连接无法从connection pool 及时获取到。(因为您根本就不需要使用到connection pool)。
另外的一点备住:
Connection Lifetime
0
当连接返回pool时,它的时间和创建时间相比,如果它的存在时间超过了Connection Lifetime,它被释放。这对于新加入集群的服务器平衡是很有用的。值0可以保证连接有最大时限。
Connection Reset
'true'
决定从pool移走时数据库连接是否被重置。
Enlist
'true'
为true时pooler自动列出当前创建线程的操作上下文,如果操作上下文存在的话。
Max Pool Size
100
Pool中允许的最大连接数。
Min Pool Size
0
Pool中允许的最小连接数。
Pooling
'true'
为true时,连接从相应的pool中被取出,如果需要将创建或添加到相应的池中。