C# 连接池(Connection Pool)的一些个人见解

原文标题:关于ADO.Net连接池(Connection Pool)的一些个人见解
一下是原文:
建立池连接可以显著提高应用程序的性能和可缩放性。SQL Server .NET Framework 数据提供程序自动为 ADO.NET 客户端应用程序提供连接池(MSDN)。
Opening a database connection is a resource intensive and time consuming operation. Connection pooling increases the performance of Web/windows applications by reusing active database connections instead of creating a new connection with every request. Connection pool manager maintains a pool of open database connections. When a new connection requests come in, the pool manager checks if the pool contains any unused connections and returns one if available. If all connections currently in the pool are busy and the maximum pool size has not been reached, the new connection is created and added to the pool. When the pool reaches its maximum size all new connection requests are being queued up until a connection in the pool becomes available or the connection attempt times out.
Connection pooling behavior is controlled by the connection string parameters. Please look into MSDN documents in the reference link if you want to know further information.
 
前面是关于连接池知识的一些基本介绍,下面的内容是重点,也是个人见解。因为没有这方面的文档资料参考或者佐证,所以请各位仔细思考和讨论(我不想误导大家)。
下面分2种情况进行讨论。
1,Client端的windows form application通过ADO.Net直接访问后台Database。
 
查看大图
SqlServer DirectConnection

我认为在这种情况下,每一个Client端和Database之间都存在一个连接池。通过设置ConnectiongString的Max Pool Size和Min Pool Size属性来验证。
如Max Pool Size = 5, Min Pool Size = 3, 通过SQL Server的SP_WHO2可以检测导如下结果:
启动1个Client端的Windows form application:application和SQL Server之间存在3个connection。
启动2个Client端的Windows form application:application和SQL Server之间存在6个connection。
启动3个Client端的Windows form application:application和SQL Server之间存在9个connection。
 
由此可见,每一个Client端和SQL Server之间都存在一个连接池,否则9个connection已经超出了Max Pool Size的设定。
 
2,Client端的application不直接通过ADO.Net来访问后台Database,而是通过IIS Server来同后台Database Server打交道。
至少有如下2种情况:
(1)Client通过IE访问部署在IIS中的web application,web application中的Data Access Class进一步访问后台Database Server.
(2)Client端的windows form application访问部署在IIS中的Remote Object,Remote Object进一步访问后台Database Server.
 
查看大图
SqlServer InDirectConnection
下面进行同样的测试:通过设置ConnectiongString的Max Pool Size和Min Pool Size属性来验证。
如Max Pool Size = 5, Min Pool Size = 3, 通过SQL Server的SP_WHO2可以检测导如下结果:
启动1个Client端的Web form application:application和SQL Server之间存在3个connection。
启动2个Client端的Web form application:application和SQL Server之间存在3个connection。
启动3个Client端的Web form application:application和SQL Server之间存在3个connection。
调用由IIS承载的Remote Object,结果类似。只是在未启动Client端之前,发现在Remote Object与Database Server之间已经存在一个connection。
由此可见,对同一个application而言,IIS Server与Database Server之间只有存在一个连接池,与Client端的多少没有关系。
3,连接池小节
根据上面的测试结果,显然第二种情况更有利于减少无用的连接数量,提高Database Server的性能。关于.Net Remoting技术及其性能问题,可以参考如下的Reference连接,这里就不讨论了。
另外,本文是个人关于连接池的一些见解,如果观点有不正确之处,希望不要误导各位。欢迎各位在此发表意见。同意的说赞同,不同意的请提出您的看法。谢谢。
Reference Links:
(1) MSDN, ms-help://MS.VSCC.2003/MS.MSDNQTR.2003FEB.2052/cpguide/html/cpconconnectionpoolingforsqlservernetdataprovider.htm
(2) Microsoft .NET Remoting:技术概述, http://www.microsoft.com/china/MSDN/library/NetFramework/default.mspx
(3) 性能比较:.NET Remoting 与 ASP.NET Web 服务, http://www.microsoft.com/china/msdn/archives/library/dnbda/html/bdadotnetarch14.asp
========================原文结束,开可以参考另外一篇文章:NET 连接池救生员
原文的一些补充观点:
连接的创建和获取:
Q: 如果我最大连接池是5个,那么,在我的程序中,每次访问,我都会使用New一个连接,然后Open,使用以后,然后Close,那么在每次New的时候,会不会创建连接,?
A: 在每次New的时候,如果connection pool中有空闲的连接,则不会创建新连接,否则会创建。
如果超过最多连接数,还能发出New的请求,但是该请求会放入队列,直到connection pool中有空闲连接或连接请求超时。
连接的效率问题:
Q: 要是可以对连接进行控制的话,应该怎么控制,很简单,我现在每次使用数据库访问时,都会New并close,我是担心这样的效率是不是会很低啊,想在连接上提高效率,要怎么操作呢?
A: 使用connection pool时,New并不一定会创建新的连接,如果connection pool中有空闲连接,直接拿来就用。
close也只是将连接放入connection pool,供后续请求使用。因此不会有效率问题。
连接的存活时间:
当连接返回到池中时,将对它的创建时间和当前时间进行比较,如果时间间隔超过由 Connection Lifetime 指定的值(以秒为单位),则会毁坏该连接。在聚集配置中可以使用它来强制在运行服务器和刚联机的服务器之间达到负载平衡。
如果值为零 (0),则将使池连接具有最大的超时期限。默认值为0
SqlConnection.Close和.Dispose的比较:
sqlconnection.close和.dispose的相同地方是:
dispose肯定调用了close,所以close里面有做的事情,dispose都包括
dispose还做了其他的资源的释放,这样在GC第一次回收的时候,省却了dispose的步骤,加快了内存资源的回收速度。
如果没有调用dispose,GC将在第一次回收先做dispose。
如果程序频繁做new sqlconnection(),然后很快就做close并且不再使用这个connection(例如一些com+/asp.net/web service的程序),而且又不做dispose,那么随着这个程序被多次调用,被分配但未能尽快释放的系统资源(通常是内存)会有很多,GC被迫回收的次数也相应增多,系统的整体效率就会变低(GC回收的时候是所有.net程序都暂停,等GC回收跑完才可以继续,造成其他程序也受到影响)。所以对于这种情况,做dispose好。
但如果其他情况,例如Winforms的client,那么没有所谓。
最近发现本站有时候会出现连接不上服务器的现象,通过sp_who查看数据库连接时,发现网站应用程序占用了比较多的连接数,当连接数达到一定数量后,网站就会出现连接不上数据库的错误。那么可以肯定,网站出现错误的原因,是数据库连接没有及时释放的缘故——或者是开辟了太多的连接。网站的数据库访问逻辑没有集中在一个数据库访问类中,而是在用到的时候建立,用完了关闭。那么这种情况是否跟上文讲的第一种情况相同呢?上文的第一种情况应该是针对winform应用程序来说的。但是本站的程序没有想上文说的第二种情况,把数据访问集中在一个数据访问类中。这个问题有待实践来验证。
 来源:http://blogs.msdn.com/angelsb/archive/2004/08/25/220333.aspx

作者:angelsb


System.InvalidOperationException: 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.

Timeout expired 异常是个很棘手的异常,想必几乎每个人都碰到过。有时可真是对它咬牙切齿,拿它没办法。 angelsb这篇文章很好,希望对大家有用。我也是看到他讲得很好,才翻译过来的,水平有限,请多多指教.


System.InvalidOperationException: 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.

哎!在另一个进程中,又出现了连接池已满的问题,这是个最让人头痛却又是最常出现的连接池问题之一.原因是在开发过程中很少碰到这个头痛的问题,但在部署APP到客户端时,却总是不经意地跑出来了.我想,我应该花些许时间对这个问题进行一次完整的总结吧.

发生的本质是什么?

我们来认真看一下可能会发生这种异常的两种情况

1) 你使用了超过最大的连接池连接数(默认的最大连接数是100)

在大部分应用程序中,这种情况是很少出现的. 毕竟当你使用连接池时,100个并行连接是一个非常大的数字.根据我的经验,会造成这种异常的原因的最大可能,应该是在一个纯种下打开了100个连接.

程序代码 程序代码


SqlConnection[] connectionArray = new SqlConnection[101];
    for (int i = 0; i <= 100; i++)
    {
        connectionArray[i] = new SqlConnection("Server=.//SQLEXPRESS ;Integrated security=sspi;connection timeout=5");
        connectionArray[i].Open();
    }




解决方案:如果你确定你将会使用超过100个并行连接(在同一连接字符串上),你可以增加最大连接数.

2) 连接泄漏

我个人认为的连接泄漏定义是你打开了一个连接但你没有在你的代码中执行close()或dispose().这范围不仅仅是你忘记了在connection后连接后使用dispose()或close()对期进行关闭,还包括一些你已经在相关connection后写好了close()却根本没有起作用的情況.我们来看看下面的代码:

程序代码 程序代码


using System;
using System.Data;
using System.Data.SqlClient;

public class Repro
{
    public static int Main(string[] args)
    {
        Repro repro = new Repro();
        for (int i = 0; i <= 5000; i++)
        {
            try{ Console.Write(i+" ");    repro.LeakConnections(); }
            catch (SqlException){}
        }

        return 1;
    }
    public void LeakConnections()
    {    
        SqlConnection sqlconnection1 = new SqlConnection("Server=.//SQLEXPRESS ;Integrated security=sspi;connection timeout=5");
        sqlconnection1.Open();
        SqlCommand sqlcommand1 = sqlconnection1.CreateCommand();
        sqlcommand1.CommandText = "raiserror ('This is a fake exception', 17,1)";
        sqlcommand1.ExecuteNonQuery();  //this throws a SqlException every time it is called.
        sqlconnection1.Close(); //We are calling connection close, and we are still leaking connections (see above comment for explanation)
    }
}




这就是一个典型的例子,将这段代码复制到visual Studio中,在 sqlconnection1.close()中设置一个断点,编译时可以看到他永远没有执行,因为ExecuteNonQurery抛出了一个异常.之后你应该可以看到恐怖的超时异常了. 在我的机子上,大约有170个连接被打开. 我曾想让其在每次调用的时候将异常抛出来达到降低连接超时出现的机率,但当你考虑到将其部署到一个ASP.NET的应用程序的时候,任何一个泄漏都将让你处于麻烦之中.

3)你是通过visual Studio中的sql debugging 来打开或关闭连接的

这是一个众所周知的Bug,可以看一下下面这个链接
http://support.microsoft.com/default.aspx?scid=kb;en-us;830118



如何在ADO.NET2.0中判断是否是连接泄漏

在1.0或1.1中,我们很难去判断是否是连接泄漏,至多可以通过一些性能指标或诸如此类的工作去实现.但在ADO.NET2.0中,如果你注意到NumberOfReclaimedConnections这个玩艺儿,就可以知道你的应用程序是否是连接泄漏了.


时刻注意修复相关的连接字符串

修改相关的连接字符串可以让你暂时翻译”逃过”一些异常,这是非常诱人的.特别是在一个高性能消耗时,修改它就显示更为必要了.


这里是一些让你的应用程序能”运行良好”的非正常行为(搬起石头砸自己的脚)

不要把Poooling=False

坦白的说,如果你将pooling设为关闭状态,你当然不会再碰到超时异常,可怕的是你的应用程序性能将大大降低,而你的连接仍然处于泄漏状态.

不要把Connection LifeTime=1

这不是一个能清除异常的方法,但它可能是最接近的一个解决方法.你想告诉我们的是将所有的连接超过一秒钟的连接都通通抛弃(正常的生命周期结束应该是在connetcio.close()后).我个人认为这种方法上关闭连接池没什么两样.除非你是在使用数据库的集群,否则你不应设置连接周期来达到目的.


不要将 Connection TimeOut=40000


非常愚蠢的选择,你这是在告诉我们在抛出一个超时异常之前,你在无限地等待一个连接转变为可用的.幸亏在ASP.NET中将会在三分钟之后取消一个进程.


不要将Max PoolSize=4000;
如果你将连接池的最大数设置到足够大的时候,你最终会将这异常停止.但在另一方面,你将占用了你的应用程序中才是真正需要的巨大的连接资源,这种做法只能饮鸠止渴.



解决方案:

你需要保证你每次调用连接的同时都在使用过后通过close()或dispose()对其执行了关闭.最简单的办法就是使用using,将你的连接泄漏方法修改成如下面的代码样式:

public void DoesNotLeakConnections()
    {    
        Using (SqlConnection sqlconnection1 = new SqlConnection("Server=.//SQLEXPRESS ;Integrated security=sspi;connection timeout=5")) {
            sqlconnection1.Open();
            SqlCommand sqlcommand1 = sqlconnection1.CreateCommand();
            sqlcommand1.CommandText = "raiserror ('This is a fake exception', 17,1)";
            sqlcommand1.ExecuteNonQuery();  //this throws a SqlException every time it is called.
            sqlconnection1.Close(); //Still never gets called.
                  } // Here sqlconnection1.Dispose is _guaranteed_
    }



FAQ:

Q:为什么要这样做

A:使用using结构等同于Try/…/Finally{ <using object>.Dispose() ) 即使当ExecuteNonQuery会抛出一个执行错误时,我们都可以保证finally模块将会执行



Q:我上面的代码中如果没有异常抛出的话,我可以使用close()或dispose()吗

A:我们毫无顾忌地用他们中的任意一个,或两个同时使用.在一个已经close或dipose()的连接中使用close()或dispose()是不会影响的

Q:Close()和Dispose()有什么不同,我应该用哪一个好?
A:它们做的是同一件事,你可以调用他们中的任意一个,或两个同时使用.

Q:你所说的"practically the same thing”是什么意思?
A:Dispose()将会通过sqlConnection来清理相关的连接,之后执行close().它们没有什么本质的区别,你可以通过reflector来证明这点

Q:与close()相比,connection.dispose()会将连接些移除吗?
A:不会

---------------------------------------------------------------

我的分享:

针对"Timeout expired"这个异常,我也查阅了很多资料。在国内我们很多project都会采用MS提供的sqlhelper这个封装类。因为这个类中有本身的缺陷所致,所以出现的"Timeout expiered"异常机率大。我在国外的一篇文章中看到的解决方案是:

将SqlHelper中的cmd.CommandTimeout="你要设置的秒数"加上去,重新编译.



if (trans != null)
cmd.Transaction = trans;

cmd.CommandType = cmdType;
cmd.CommandTimeout = 240;


通过搜索,我找到了相关的一个代码.
这个代码是摘自国人的某位同行的,感谢
http://blog.csdn.net/long2006sky/archive/2007/07/09/1683459.aspx

eg:

** <summary>
    /// 执行查询语句,返回DataTable
    /// </summary>
    /// <param name="SQLString">查询语句</param>
    /// <param name="commTime">设置查询Timeout</param>
    /// <returns>用于复杂查询</returns>
    public static DataTable GetDataTable(string SQLString,int commTime)
    ...{
        string connectionString = System.Configuration.ConfigurationManager.AppSettings["connectionString"];
        using (System.Data.SqlClient.SqlConnection connection = new System.Data.SqlClient.SqlConnection(connectionString))
        ...{
            DataTable dt = new DataTable();
            try
            ...{
                connection.Open();
                System.Data.SqlClient.SqlDataAdapter da = new System.Data.SqlClient.SqlDataAdapter();
                System.Data.SqlClient.SqlCommand comm = new System.Data.SqlClient.SqlCommand(SQLString, connection);
                comm.CommandTimeout = commTime;
                da.SelectCommand = comm;
                da.Fill(dt);
            }
            catch (System.Data.SqlClient.SqlException ex)
            ...{
                throw new Exception(ex.Message);
            }
            return dt;
        }
    }

asp.net如何设置数据库连接池的数量?

使用一组名称-值对以链接字符串的形式配置链接池。例如,可以配置池是否有效(默认是有效的),池的最大、最小容量,用于打
开链接的排队请求被阻断的时间。下面的示例字符串配置了池的最大和最小容量。

"Server=(local); Integrated Security=SSPI; Database=Northwind;
Max Pool Size=75; Min Pool Size=5"

摘要


连接池允许应用程序从连接池中获得一个连接并使用这个连接,而不需要为每一个连接请求重新建立一个连接。一旦一个新的连接被创建

并且放置在连接池中,应用程序就可以重复使用这个连接而不必实施整个数据库连接创建过程。

当应用程序请求一个连接时,连接池为该应用程序分配一个连接而不是重新建立一个连接;当应用程序使用完连接后,该连接被归还给连接

池而不是直接释放。

如何实现连接池

确保你每一次的连接使用相同的连接字符串(和连接池相同);只有连接字符串相同时连接池才会工作。如果连接字符串不相同,应用程序

就不会使用连接池而是创建一个新的连接。

优点

        使用连接池的最主要的优点是性能。创建一个新的数据库连接所耗费的时间主要取决于网络的速度以及应用程序和数据库服务器的

(网络)距离,而且这个过程通常是一个很耗时的过程。而采用数据库连接池后,数据库连接请求可以直接通过连接池满足而不需要为该请

求重新连接、认证到数据库服务器,这样就节省了时间。

缺点

       数据库连接池中可能存在着多个没有被使用的连接一直连接着数据库(这意味着资源的浪费)。

技巧和提示

1.   当你需要数据库连接时才去创建连接池,而不是提前建立。一旦你使用完连接立即关闭它,不要等到垃圾收集器来处理它。

2.   在关闭数据库连接前确保关闭了所有用户定义的事务。

3.   不要关闭数据库中所有的连接,至少保证连接池中有一个连接可用。如果内存和其他资源是你必须首先考虑的问题,可以关闭所有的连

接,然后在下一个请求到来时创建连接池。

连接池FAQ

1.   何时创建连接池?

当第一个连接请求到来时创建连接池;连接池的建立由数据库连接的连接字符创来决定。每一个连接池都与一个不同的连接字符串相关。

当一个新的连接请求到来时如果连接字符串和连接池使用的字符串相同,就从连接池取出一个连接;如果不相同,就新建一个连接池。

2.   何时关闭连接池?

当连接池中的所有连接都已经关闭时关闭连接池。

3.   当连接池中的连接都已经用完,而有新的连接请求到来时会发生什么?

当连接池已经达到它的最大连接数目时,有新的连接请求到来时,新的连接请求将放置到连接队列中。当有连接释放给连接池时,连接池将

新释放的连接分配给在队列中排队的连接请求。你可以调用close和dispose将连接归还给连接池。

4.   我应该如何允许连接池?

对于.NET应用程序而言,默认为允许连接池。(这意味着你可以不必为这件事情做任何的事情)当然,如果你可以在SQLConnection对象的连

接字符串中加进Pooling=true;确保你的应用程序允许连接池的使用。

5.   我应该如何禁止连接池?
ADO.NET默认为允许数据库连接池,如果你希望禁止连接池,可以使用如下的方式:
1) 使用SQLConnection对象时,往连接字符串加入如下内容:Pooling=False;
2) 使用OLEDBConnection对象时,往连接字符串加入如下内容:OLE DB Services=-4;

 

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页