非常老的话题 SQLSERVER连接池
写这篇文章不是说要炒冷饭,因为园子里有非常非常多关于SQLSERVER连接池的文章,但是他们说的都是引用MSDN里的解释
或者自己做一些测试试验一下连接池的性能。但是对于SQLSERVER连接池的内部机制,好像都没有非常清晰地说清楚。
连接池的作用就不说了,在园子里随便搜一大堆
还有使用连接池有利也有弊,大部分文章都说连接池的好处没有说连接池的不好的地方
连接池不好的地方在于事务的提交方面,如果上一个连接没有提交事务,有可能下一个
连接会遇到因为上一个连接的事务没有提交从而遇到意想不到的后果
详细的可以自行GOOGLE
先说一下带连接池功能的编程接口
微软的SQLSERVER客户端编程接口:
ADO和ADO.NET 都支持连接池这种机制
JAVA的JDBC也支持连接池这种机制
注意:连接池机制是客户端数据库驱动程序提供的,功能都在驱动程序里,所以JDBC跟ADO,ADO.NET的连接池功能会有区别
因为客户端应用程序都是通过加载SQLSERVER的数据驱动控件做SQLSERVER连接。目前客户端数据库驱动程序主要有3种:
1、MDAC(微软数据访问组件) SQL2000的时候开始有的
2、SQLSERVER Native Client SQL2005开始引入的
3、Microsoft JDBC Provider 使用机制与MDAC和SQLSERVER Native Client不同
下面再说一下在论坛里经常有人问到的问题:
问题1:SQLServer连接池被创建在SQLClient端还是SQLServer端?
答案:在客户端
当应用程序运行的时候,会有一个连接池的管理控件运行在应用程序的进程里,统一管理应用程序和SQLSERVER建立的所有连接,
并且维护这些连接一直处于活动状态。当有用户发出一个connection open指令时连接池会在自己维护的连接池中找一个处于空闲状态
的连接放回自己管理的连接池里,给这个用户使用。当用户使用完毕后,发出connection close指令,连接池会把这个连接放回自己
管理的连接池里,让他重新处于空闲状态,而不是真的从SQL里登出。这样如果下次有用户需要相同连接,就可以重用这个连接,
而无须再去做物理连接了。就是说连接池是放在客户端的,是客户端机制
问题2:如果在一个应用程序里设置连接池的大小为40000个,第二个应用程序里设置连接池的大小也为40000个,
程序跟SQLSERVER会不会报错?
答案:不会
当应用程序运行的时候,会有一个连接池的管理控件运行在应用程序的进程里,统一管理应用程序和SQLSERVER建立的所有连接,
并且维护这些连接一直处于活动状态。当有用户发出一个connection open指令时连接池会在自己维护的连接池中找一个处于空闲状态
的连接放回自己管理的连接池里,给这个用户使用。当用户使用完毕后,发出connection close指令,连接池会把这个连接放回自己
管理的连接池里,让他重新处于空闲状态,而不是真的从SQL里登出。这样如果下次有用户需要相同连接,就可以重用这个连接,
而无须再去做物理连接了
这个问题是针对上面这段话的,如果一个应用程序指定了连接池的大小为40000个那么跟SQLSERVER的连接已经有40000个了,
SQLSERVER的最大连接数是32767,那么第二个应用程序再打开40000个连接会不会报错呢?
本人根据园子里的这篇文章做了一个实验测试会不会报错,修改了他里面的一些代码,下面是我自己写的代码跟报错的内容跟文章链接
做实验之前记得重启一下SQLSERVER
亲测SQLServer的最大连接数
http://www.cnblogs.com/wlb/archive/2012/04/08/2437617.html
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Data; 6 using System.Data.SqlClient; 7 8 namespace SQLServerMaxConnectionTest 9 { 10 class Program 11 { 12 static void Main(string[] args) 13 { 14 int maxCount = 40000; 15 string connstr="Server=joe;Database=AdventureWorks;User ID=sa;Password=test;pooling=true;connection lifetime=0;min pool size = 1;max pool size=40000"; 16 List<SqlConnection> collection = new List<SqlConnection>(); 17 for (int i = 0; i < maxCount; i++) 18 { 19 Console.WriteLine(string.Format("成功创建连接对象{0}",i)); 20 try 21 { 22 var db = new SqlConnection(connstr); 23 db.Open(); 24 collection.Add(db); 25 } 26 catch (Exception ex) 27 { 28 WriteErrLog.AppendErrLog(ex.ToString()); 29 } 30 31 } 32 } 33 } 34 }
1 2012-12-01 17:04:22 System.Data.SqlClient.SqlException: 当前命令发生了严重错误。应放弃任何可能产生的结果。 2 在 System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection) 3 在 System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj) 4 在 System.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj) 5 在 System.Data.SqlClient.SqlInternalConnectionTds.CompleteLogin(Boolean enlistOK) 6 在 System.Data.SqlClient.SqlInternalConnectionTds.AttemptOneLogin(ServerInfo serverInfo, String newPassword, Boolean ignoreSniOpenTimeout, Int64 timerExpire, SqlConnection owningObject)
由于本人的笔记本性能不给力,在cmd里面打开的对象数达到16531 另一个是16124的时候差不多死机了,所以未能截图。
16531+16124 差不多达到32767,测试结果表明,如果有程序一早打开了一定数目的连接,那么第二个应用程序就算
再打开40000个连接是没有用的并会报错,第二个应用程序能打开的连接数目等于SQLSERVER最大连接数减去第一个应用程序
打开的连接数
有一个问题非常奇怪:两个应用程序都可以指定连接池的最大连接数为40000,如果是这样的话应该SQLSERVER应该预先
保持了80000个活动连接,让应用程序连接进来,这样SQLSERVER应该会报错,但是我用下面代码测试了,发现没有报错
测试方法跟上面的那个测试一样,做下面实验之前请重启一下SQLSERVER
这个问题已经有答案了,答案在文章的结尾o(∩_∩)o
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Data; 6 using System.Data.SqlClient; 7 8 namespace SQLServerMaxConnectionTest 9 { 10 class Program 11 { 12 static void Main(string[] args) 13 { 14 //改成500个连接 15 int maxCount = 500; 16 string connstr="Server=joe;Database=AdventureWorks;User ID=sa;Password=test;pooling=true;connection lifetime=0;min pool size = 1;max pool size=40000"; 17 List<SqlConnection> collection = new List<SqlConnection>(); 18 for (int i = 0; i < maxCount; i++) 19 { 20 Console.WriteLine(string.Format("成功创建连接对象{0}",i)); 21 try 22 { 23 var db = new SqlConnection(connstr); 24 db.Open(); 25 collection.Add(db); 26 } 27 catch (Exception ex) 28 { 29 WriteErrLog.AppendErrLog(ex.ToString()); 30 } 31 32 } 33 } 34 } 35 }
希望高人可以解答一下
问题3:连接池字符串里要不要写pooling=true;connection lifetime=0;min pool size = 1;?
答案:个人觉得没有必要写,连接池默认是开启的,连接的生命周期没有必要指定,因为你都不知道
自己的SQL语句要运行多长时间,连接池的min pool size不需要指定了,只需要指定max pool size=32767
就可以了
问题4:sp_reset_connection 经常有人在论坛里问,使用SQL Trace的时候会有大量的sp_reset_connection
这个存储过程的执行,这个存储过程到底是什么来的?
网上对这个存储过程的资料很少,MSDN也找不到资料
答案:我们知道,每个连接都会维护自己的一些独有资源,比如临时表,变量,游标等,
他也有可能会修改一些默认的设置,例如事务隔离级别等。如果不同用户先后使用同一个连接,
会不会前一个用户设置的状态或申请的资源影响后一个用户正常使用呢?如果连接池的管理者
不做特殊处理,的确会有这种情况发生。所以微软的连接池技术里包括ADO.NET和ADO,
引入了一个特殊的指令:sp_reset_connection,来清除前一个用户做的绝大多数设置,避免这种问题。
sp_reset_connection会在SQLSERVER里做些什么?
1、清除连接现有所有内部数据结构。包括:
(1)清除所有openxml打开的document句柄
(2)关闭所有的游标(cursor)
(3)释放所有SQL语句句柄
(4)清除所有临时对象(临时表等)
(5)释放连接持有的所有锁
(6)清除缓存的所有安全上下文信息(security context)
2、重置连接设置。包括:
(1)重置连接的SQL Trace标志值(例如1204,1222,3604等)
(2)重置所有"SET" 选项值(SET IMPLICIT_TRANSACTIONS ON 等)
(3)重置连接的统计信息值
3、回滚所有SQLSERVER事务
需要说明的是,如果连接当前参与了一个由客户端发起的分布式事务,这个分布式事务不会受到影响。
在SQL里的事务还会被保留
4、把当前数据库切换到用户默认数据库
5、SQLSERVER会再次检查当前用户是否有权做数据库连接。如果这个权力已被移除
SQLSERVER会中断这个物理连接(这样防止一个已经被取消访问权的用户还能长时间
使用数据库的问题)
完成这些事情以后,一个连接基本上已经和他先前做的事情不再有任何关系(分布式事务除外)
可以说,这个连接和一个新的连接已经没有什么大的区别了。通过这些,应用程序的用户
可以尽快地拿到和释放连接,而SQLSERVER这边也不会因为连接的重复使用而产生相互影响的问题
准备下班了 有问题的童鞋可以发评论,希望大家拍砖o(∩_∩)o
刚才测试了一下,在SSMS里运行下面语句
1 SELECT [program_name] ,[spid] FROM [sys].[sysprocesses] WHERE [spid]>50
然后运行上面我给出的示例代码程序跟SQLSERVER都在同一台电脑,改为500个连接那个,我双击打开了很多个cmd程序,
发现在cmd程序运行的时候会看到大量的连接在SSMS里面
当那些cmd程序全部运行完毕之后,这些进程就在SQLSERVER里消失
那么上面的问题:
两个应用程序都可以指定连接池的最大连接数为40000,如果是这样的话应该SQLSERVER应该预先
保持了80000个活动连接,让应用程序连接进来,这样SQLSERVER应该会报错
答案:应该是连接池保持了80000个活动连接,SQLSERVER并没有保持这些活动连接
想一想这也是正确的,如果客户端指定了80000个连接,SQLSERVER就要一次打开
80000个连接(实际数量是32767个)并对这些连接进行维护,那么SQLSERVER肯定
慢得要命。因为客户端的连接并没有达到80000个那么多,你在连接字符串里指定80000个
连接,实际上只是指定连接池的连接数
客户端的连接池和服务器端的线程池的区别
客户端连接池即使是40000个,也只是一个设定值,并不是说当前程序打开了40000个长连接
即使真的有40000个连接到SQL Server,SQL Server同一时刻也只能保持32767个长连接