System.Data.SQLClient.SqlConnection在Open之后为什么需要及时Close?

如果System.Data.SQLClient.SqlConnection(下面简称dbconn)不及时Close,那么造成的后果可以在这里看到:http://www.cnblogs.com/juqiang/archive/2005/12/10/294269.html

具体的原因,我们来看代码(用reflector反编译.net framework 2.0的dll)

dbconn有一个方法叫做:Open,部分代码如下:
 1 try
 2        {
 3            statistics = SqlStatistics.StartTimer(this.Statistics);
 4            this.InnerConnection.OpenConnection(this, this.ConnectionFactory);
 5            SqlInternalConnectionSmi innerConnection = this.InnerConnection as SqlInternalConnectionSmi;
 6            if (innerConnection != null)
 7            {
 8                innerConnection.AutomaticEnlistment();
 9            }

10
注意上面的第4行,this.InnerConnection.OpenConnection(thisthis.ConnectionFactory);这里的this.ConnectionFactory是一个DbConnectionFactory类型,定义在class SqlConnection中
static SqlConnection()
{
    EventInfoMessage 
= new object();
    _connectionFactory = SqlConnectionFactory.SingletonInstance;
    ExecutePermission = CreateExecutePermission();
}
在看第一段代码的第4行,这里用InnerConnection来进行OpenConnection,那么InnerConnection的类型,我们可以从下面看到:
public SqlConnection()
{
    
this.ObjectID = Interlocked.Increment(ref _objectTypeCount);
    GC.SuppressFinalize(
this);
    
this._innerConnection = DbConnectionClosedNeverOpened.SingletonInstance;
}
它是一个DbConnectionClosedNeverOpened类型(具体做啥的,我们不管),它的基类是:DbConnectionClose。ok,确定好上述两个类型后,我们就看一下OpenConnection的代码:(class DbConnectionClosed)
 1  try
 2        {
 3            connectionFactory.PermissionDemand(outerConnection);
 4            to = connectionFactory.GetConnection(outerConnection);
 5        }

 6        catch
 7        {
 8            connectionFactory.SetInnerConnectionTo(outerConnection, this);
 9            throw;
10        }
看一下第4行,哈,这里绕到了我们上面的conn factory,我们看它里面的GetConnection怎么写的?(class DbConnectionFactory)
 1internal DbConnectionInternal GetConnection(DbConnection owningConnection)
 2{
 3    DbConnectionInternal connection;
 4    DbConnectionPoolGroup connectionPoolGroup = this.GetConnectionPoolGroup(owningConnection);
 5    DbConnectionPool connectionPool = this.GetConnectionPool(owningConnection, connectionPoolGroup);
 6    if (connectionPool == null)
 7    {
 8        connectionPoolGroup = this.GetConnectionPoolGroup(owningConnection);
 9        connection = this.CreateNonPooledConnection(owningConnection, connectionPoolGroup);
10        this.PerformanceCounters.NumberOfNonPooledConnections.Increment();
11        return connection;
12    }

13    connection = connectionPool.GetConnection(owningConnection);
14    if (connection == null)
15    {
16        Bid.Trace("<prov.DbConnectionFactory.GetConnection|RES|CPOOL> %d#, GetConnection failed because a pool timeout occurred.\n"this.ObjectID);
17        throw ADP.PooledOpenTimeout();
18    }

19    return connection;
20}
注意第5行里面的GetConnectionPool的调用,继续看它内部的代码:(class DbConnectionFactory)
 1private DbConnectionPool GetConnectionPool(DbConnection owningObject, DbConnectionPoolGroup connectionPoolGroup)
 2{
 3    if (connectionPoolGroup.IsDisabled && (connectionPoolGroup.PoolGroupOptions != null))
 4    {
 5        Bid.Trace("<prov.DbConnectionFactory.GetConnectionPool|RES|INFO|CPOOL> %d#, DisabledPoolGroup=%d#\n"this.ObjectID, connectionPoolGroup.ObjectID);
 6        DbConnectionPoolGroupOptions poolGroupOptions = connectionPoolGroup.PoolGroupOptions;
 7        DbConnectionOptions connectionOptions = connectionPoolGroup.ConnectionOptions;
 8        string connectionString = connectionOptions.UsersConnectionString(false);
 9        connectionPoolGroup = this.GetConnectionPoolGroup(connectionString, poolGroupOptions, ref connectionOptions);
10        this.SetConnectionPoolGroup(owningObject, connectionPoolGroup);
11    }

12    return connectionPoolGroup.GetConnectionPool(this);
13}
注意第9行,我们go on...(Class DbConnectionFactory)
 1        if ((poolOptions == null&& ADP.IsWindowsNT)
 2        {
 3            if (group != null)
 4            {
 5                poolOptions = group.PoolGroupOptions;
 6            }

 7            else
 8            {
 9                poolOptions = this.CreateConnectionPoolGroupOptions(options);
10            }

11        }

12
依然是第9行(枯燥的数字。。。),继续往里面看:(Class DbConnectionFactory)
protected override DbConnectionPoolGroupOptions CreateConnectionPoolGroupOptions(DbConnectionOptions connectionOptions)
{
    SqlConnectionString str 
= (SqlConnectionString) connectionOptions;
    
if (str.ContextConnection || !str.Pooling)
    
{
        
return null;
    }

    
int connectTimeout = str.ConnectTimeout;
    
if ((0 < connectTimeout) && (connectTimeout < 0x20c49b))
    
{
        connectTimeout 
*= 0x3e8;
    }

    
else if (connectTimeout >= 0x20c49b)
    
{
        connectTimeout 
= 0x7fffffff;
    }

    
return new DbConnectionPoolGroupOptions(str.IntegratedSecurity, str.MinPoolSize, str.MaxPoolSize, connectTimeout, str.LoadBalanceTimeout, str.Enlist, false);
}
看最后一行,调用了一个new操作,注意里面的参数!有MaxPoolSize,MinPoolSize等属性。而这些重要属性是从str,即SqlConnectionString的一个property(Class DbConnectionPool,快到终点了,坚持一下。。。)
  1internal SqlConnectionString(string connectionString) : base(connectionString, GetParseSynonyms(), false)
  2{
  3    bool inProc = InOutOfProcHelper.InProc;
  4    this._integratedSecurity = base.ConvertValueToIntegratedSecurity();
  5    this._async = base.ConvertValueToBoolean("asynchronous processing"false);
  6    this._connectionReset = base.ConvertValueToBoolean("connection reset"true);
  7    this._contextConnection = base.ConvertValueToBoolean("context connection"false);
  8    this._encrypt = base.ConvertValueToBoolean("encrypt"false);
  9    this._enlist = base.ConvertValueToBoolean("enlist", ADP.IsWindowsNT);
 10    this._mars = base.ConvertValueToBoolean("multipleactiveresultsets"false);
 11    this._persistSecurityInfo = base.ConvertValueToBoolean("persist security info"false);
 12    this._pooling = base.ConvertValueToBoolean("pooling"true);
 13    this._replication = base.ConvertValueToBoolean("replication"false);
 14    this._userInstance = base.ConvertValueToBoolean("user instance"false);
 15    this._connectTimeout = base.ConvertValueToInt32("connect timeout", 15);
 16    this._loadBalanceTimeout = base.ConvertValueToInt32("load balance timeout"0);
 17    this._maxPoolSize = base.ConvertValueToInt32("max pool size", 100);
 18    this._minPoolSize = base.ConvertValueToInt32("min pool size"0);
 19    this._packetSize = base.ConvertValueToInt32("packet size"0x1f40);
===============================其他代码省略========================================
181    }
182}
这段代码很长,但是我们注意上面的第17行,这里有一个默认值100,就是说,如果你没有在connection string中指定max pool size,那么该值就是100。
那么,如果我们代码中对于dbconn进行了Open,没有进行Close,conn pool就会一直增长,一直涨到100,涨到这个Max Pool Size。原因我们看下面:(Class DbConnectionPool)
 1            case 2:
 2                Bid.PoolerTrace("<prov.DbConnectionPool.GetConnection|RES|CPOOL> %d#, Creating new connection.\n"this.ObjectID);
 3                try
 4                {
 5                    fromTransactedPool = this.UserCreateRequest(owningObject);
 6                }

 7                catch
 8                {
 9                    if (fromTransactedPool == null)
10                    {
11                        Interlocked.Decrement(ref this._waitCount);
12                    }

13                    throw;
14                }

15
注意上面的第5行,UserCreateRequest,看这个方法:
private DbConnectionInternal UserCreateRequest(DbConnection owningObject)
{
    DbConnectionInternal internal2 
= null;
    
if (this.ErrorOccurred)
    
{
        
throw this._resError;
    }

    
if ((this.Count >= this.MaxPoolSize) && (this.MaxPoolSize != 0))
    {
        
return internal2;
    }

    
if (((this.Count & 1!= 1&& this.ReclaimEmancipatedObjects())
    
{
        
return internal2;
    }

    
return this.CreateObject(owningObject);
}
上面的代码很清楚的表明,如果count>=MaxPoolsize,会返回一个internal2,而这个field是一个Null!从其他的地方,我们也能找到类似的判断。

上面写了这么多,结论就是,如果dbconn在Open之后,没有Close,那么会造成conn个数上涨,到100之后就会停下来。第101个链接的请求,是无法创建成功的。这样,db和web server很可能都是CPU很低,如0%,但是客户端的响应时间就是很长,造成性能下降。

posted on 2008-03-09 19:46 鞠强 阅读(2302) 评论(16)  编辑 收藏 所属分类: .NET CLR研究

评论

#1楼  2008-03-09 19:48 Justin      

沙发,拜读!   回复  引用  查看    

#2楼  2008-03-09 19:59 Jason_110 [未注册用户]

如果用单例模式,保持一个Connection一直Open,性能是否会比较高呢。
而且,我这里只做查询的操作。   回复  引用  查看    

#3楼  2008-03-09 20:08 rosanshao      

保持一个Connection一直Open,性能是否会比较高呢
如果保持Open,得不偿失,反正低得很   回复  引用  查看    

#4楼  2008-03-09 20:09 lbq1221119      

这篇文章着实不错

刚刚在看上一篇文章提到的“100”的问题也看到了答案,100应该是不可设置的appdomain的threadpool的默认大小。

顶了   回复  引用  查看    

#5楼  2008-03-09 20:14 lbq1221119      

@rosanshao
数据库的连接池里面好像是这么做的吧,如果一个连接字符串是相同的,就在数据库服务器的连接池上面保留这个连接   回复  引用  查看    

#6楼  2008-03-09 20:21 深蓝      

分析的很深,受用了,鞠强厉害!   回复  引用  查看    

#7楼  2008-03-09 20:21 Q.Lee.lulu      

实在没耐心看完
就直接看结果了,呵呵   回复  引用  查看    

#8楼  2008-03-09 20:57 Justin      

很透彻!要好好向老大哥学习这种Debug精神!经过这么一折腾,问题清楚多了,再遇到类似问题就不会束手无策了!   回复  引用  查看    

#9楼  2008-03-09 21:32 金色海洋(jyk)      

佩服呀,这些代码,我看着就头痛,坚持看完了,根据讲述了解了。感觉好像是内部代码写得不够好才照成了,超过100的时候的性能下降。为什么不在超过100的时候跑出个异常呢?或者使停止服务。或者,


另外有一个地方不是很明确:close 是把一个连接放回了连接池,还是销毁了一个连接。

对了还有一个:当new出来一个 SqlConnection 的实例的时候(没有open),有没有从连接池里获取一个连接。

  回复  引用  查看    

#10楼  2008-03-09 21:41 Justin      

@金色海洋(jyk)
Close应该是放回连接池,连接池里的链接的生命周期应该跟连接池一样,一旦被创建就跟随连接池一直存在。
new的时候,先去连接池看是否有空闲链接,有则从空闲链接里引用一个,如果连接池里没有空闲链接,且连接池还没满(比如链接数不到100),应该是先创建连接对象,然后放到连接池里,再把这个对象分给创建者先用,用完了再放回来。   回复  引用  查看    

#11楼  2008-03-09 21:42 lbq1221119      

@金色海洋(jyk)
当new出来一个 SqlConnection 的实例的时候(没有open),有没有从连接池里获取一个连接。

这个要看你的连接字符串,如果是同一个连接字符串,直接从数据库的连接缓冲池里面获取了

一个是appdomain的threadpool,一个是db的connection pool   回复  引用  查看    

#12楼  2008-03-09 22:35 BlueMountain      

#2楼 59.57.200.* 2008-03-09 19:59 Jason_110 [未注册用户]
如果用单例模式,保持一个Connection一直Open,性能是否会比较高呢。
而且,我这里只做查询的操作。
------------------------------------------
java与模式 单件模式

这种情况当一个查询没有结束的时候另外一个查询在被提交 数据库会死锁   回复  引用  查看    

#13楼  2008-03-10 14:01 slici [未注册用户]

好家伙   回复  引用  查看    

#14楼  2008-03-10 14:05 一线风      

Oledb的好像就不是这样的了,呵~
  回复  引用  查看    

#15楼  2008-03-10 16:08 zhangxd      

连接池里面的连接open 不 open,直接在数据库服务器上:netstat -anb

..........这种情况当一个查询没有结束的时候另外一个查询在被提交 数据库会死锁

那你这两个SQL应该使用的是不同的数据库连接吧...
  回复  引用  查看    

#16楼  2008-03-12 14:56 yanchao      

恩 看来以后要注意了!   回复  引用  查看    


标题  
姓名  
主页
Email (只有博主才能看到) 
验证码 *  看不清,换一张
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
该文被作者在 2008-03-09 19:52 编辑过
 
历史上的今天:

另存  打印
最新IT新闻:
· 《福布斯》:暴雪的新一波完美风暴已经到来
· 中国互联网公司在哪儿
· 微软:Silverlight内容也可被搜索引擎检索
· 内置AI技术 三星聪明微波炉还能上网
· 盛大设文学公司 原新浪博客负责人侯小强任CEO
 


<2008年3月>
2425262728291
2345678
9101112131415
16171819202122
23242526272829
303112345

导航

统计

公告

web counter
访问量是此计数器+213636(粗略值) 大家不要给我私人留言了,经常忘记看。有事情往这里发邮件吧:juqiang@live.com,多谢!!!

与我联系

常用链接