您的位置:首页 > 理论基础 > 计算机网络

关于ado.net连接池的一些分享(2)(原文出自:http://www.cnblogs.com/b42259626/articles/968460.html)

2012-12-25 15:08 573 查看
21世纪什么最贵?数据库连接。对于以数据库做数据存储基石的应用系统来说,数据库连接是整个系统中最珍贵的资源之一。数据库连接池是为了更有效地 利用数据库连接的最重要措施。它对于一个大型的应用系统的性能至关重要,特别是Web应用。ADO.NET Data Provider(以下简称Data Provider)会帮我们管理连接池,所以有人说使用连接池就像游儿童池一样轻松。但并不是说有了Data Provider程序员就万事无忧的,不正确地使用连接池可能导致你的应用在池里淹死。笔者希望通过本文能让读者彻底明白连接池的重要性以及能根据实际情 况正确配置连接池的参数,明白实际应用中出现的连接泄漏、“死连接”等异常情况和应对方法,让应用轻松畅游连接池。本文主要介绍ADO.NET 1.1的连接池。 1、什么是连接池连接池是Data Provider提供的一个机制,使得应用程序使用的连接保存在连接池里而避免每次都要完成建立/关闭连接的完整过程。要理解连接池,先要理解程序里 SqlConnection.Open()、SqlConnection.Close()和打开/关闭一个“物理连接”的关系。Data Provider在收到连接请求时建立连接的完整过程是:先连接池里建立新的连接(即“逻辑连接”),然后建立该“逻辑连接”对应的“物理连接”。建立 “逻辑连接”一定伴随着建立“物理连接”。Data Provider关闭一个连接的完整过程是先关闭“逻辑连接”对应的“物理连接”然后销毁“逻辑连接”。销毁“逻辑连接”一定伴随着关闭“物理连接”。 SqlConnection.Open()是向Data Provider请求一个连接,Data Provider不一定需要完成建立连接的完整过程,可能只需要从连接池里取出一个可用的连接就可以;SqlConnection.Close()是请求 关闭一个连接,Data Provider不一定需要完成关闭连接的完整过程,可能只需要把连接释放回连接池就可以。下面以一个例子来说明。本文例子都使用Console Application。我们使用操作系统的性能监视器来比较使用连接池与否,数据库的“物理连接”数量的不同。因为性能监视器至少每一秒采集一次数据, 为方便观察效果,代码中Open和Close连接后都Sleep一秒。

SqlConnection con = new SqlConnection("server = .;

database= northwind;pooling = false;trusted_connection = true");
for(int i =0;i <10;i++)

{

try

{

con.Open();

System.Threading.Thread.Sleep(1000);

}

catch(Exception e){Console.WriteLine(e.Message);}

finally

{

con.Close();

System.Threading.Thread.Sleep(1000);

}

}
首先,不使用连接池做测试。以上程序中pooing = false表示不使用连接池,程序使用同一个连接串Open & Close了10次连接,使用性能计数器观察SQL Server的“物理连接”数量。从下面的锯齿图可以看出每执行一次con.Open(),SQLServer的“物理连接”数量都增加一,而每执行一次 con.Close(),SQLServer的“物理连接”数量都减少一。由于不使用连接池,每次Close连接的时候Data Provider需要把“逻辑连接”和“物理连接”都销毁了,每次Open连接的时候Data Provider需要 建立“逻辑连接”和“物理连接”,锯齿图因此而成。string connectionString = "server = .;database= northwind;user= sa;

password = sqlserver;min pool size =2;max pool size =5;connection lifetime =20;connection timeout =10";
SqlConnection[] connections = new SqlConnection[7];

for(int i =0;i < connections.Length;i++)

connections[i]= new SqlConnection(connectionString);

…Open connection[0],8秒后Open connection[1]     …8秒后Close connection[0],10秒后Open connection[0]     …5秒后Open connection[2]、[3]、[4],每隔两秒打开一个

Console.WriteLine("Now the Max Pool Size is reached and

we try toopen connection[5].\r\n");

for(int i =0;i <2;i++)

{

try

{connections[5].Open();}

catch(InvalidOperationException e)

{

if(i ==1)

return;

Console.WriteLine("Can't open connection[5].\r\n" + e.Message);

connections[4].Close();

Console.WriteLine("\r\nTry to open connection[5] again.");

continue;

}

}

Console.WriteLine("connection[5] is open.");

foreach(SqlConnection con in connections)

{

if(con.State == ConnectionState.Open)

{

con.Close();

Console.WriteLine("A connection is released back to the pool.");

System.Threading.Thread.Sleep(5000);

}

}
使用性能监视器观察,得到图4所示结果。我们观察.NET CLR Data的“SqlClient: Current # connection pools”、“SqlClient: Current # pooled connections”以及Sql Server: General Statistic的User Connections计数器。private void Method(){string conString = "server = .;database= northwind;user= sa;password = sqlserver;max pool size =10";SqlConnection con = new SqlConnection(conString);con.Open();}
如果一个应用系统里存在会泄漏连接的代码,系统运行一段时间后连接就泄漏殆尽。即使把Max Pool Size设得很大也解决不了问题,因为单是一直存在太多的数据库连接已经让人不能容忍,况且这些是不能使用的“物理连接”。要避免连接的泄漏,请注意下面几点:(1)除非使用CommandBehavior.CloseConnection作ExecuteReader参数,否则Close DataReader不会Close关联的连接。在多层结构的系统中,如果中间层向表现层返回DataReader,那么必须使用 CommandBehavior.CloseConnection作ExecuteReader参数,这样当表现层执行DataReader的Close 方法时就会Close连接,不然表现层想帮你也有心无力。(2)执行DataAdapter的Fill和Update方法时,如果连接没有打开,那么DataAdapter自动会打开连接,执行完操作后自动关闭连接;但如果连接已经打开,DataAdapter执行完操作后不会帮你关闭连接,你需要自己负责关闭连接。    5、处理“死连接”“可用的”连接一定能访问数据库?不一定。在前面“减少连接”的部分提过,在数据库被shutdown、网络中断、数据库连接进程/会话被kill情况下连接池会产生“死连接”。“死连接”指连接 池里某个连接对应的“物理连接”已经断开,但ClientApp执行Open方法时候可以从连接池取得该连接,直到执行数据库操作Data Provider才发现该连接是“死连接”。注意区分“死连接”和泄漏的连接。“死连接”是“逻辑连接”,是“可用的”连接,但该“逻辑连接”对应的“物理连接”已经不存在;泄漏的连接指“物理连接”存在而对应的“逻辑连接”实际没有被占有但被标识为“被占用”而导致该“逻辑连接”不能被使用。发现“死连接”后Data Provider会销毁该连接并抛出SqlException但不会自动尝试使用其他连接,即使在ADO.NET 2.0里也是如此。把exception catch下来,然后提示用户重新操作不是最好的处理方式。不管微软为什么不帮我们尝试其他连接,我们只能接受现实自己解决。下面例子里Helper的ExecuteReader把Data Provider抛出的SqlException catch后先把连接置为“无效”,然后再尝试使用其他连接,如果再尝试的次数达到预定值还不成功才抛出SqlException。public class Helper{private static int TimesTry =0,MaxTry =5;public static SqlDataReader ExecuteReader(string conStr,CommandType eType,string commandText){SqlConnection cn =null;SqlDataReader dr =null;SqlCommand cmd =null;try{cn = new SqlConnection(conStr);cmd = new SqlCommand(commandText,cn);cmd.CommandType = eType;cn.Open();dr = cmd.ExecuteReader(CommandBehavior.CloseConnection);}catch(SqlException e){if(dr !=null)dr.Close();cn.Close();System.Threading.Thread.Sleep(2000);if(TimesTry < MaxTry){dr = ExecuteReader(conStr,eType,commandText);TimesTry++;}else                     throw e;}return dr;}}string conString = "server = .;database= northwind;user= sa;max pool size =1;password = sqlserver;Application Name = DeadConnectionExample";SqlDataReader reader = Helper.ExecuteReader(conString,CommandType.Text,"select*from orders");reader.Close();System.Threading.Thread.Sleep(15000);SqlConnection con = new SqlConnection("server = .;database= master;user= sa;password = sqlserver;pooling = false");con.Open();SqlCommand cmd = new SqlCommand("SELECT SPID FROM master.dbo.sysprocessesWHERE PROGRAM_NAME ='DeadConnectionExample'",con);string spid = cmd.ExecuteScalar().ToString();cmd = new SqlCommand("kill " + spid,con);cmd.ExecuteNonQuery();con.Close();System.Threading.Thread.Sleep(5000);reader = Helper.ExecuteReader(conString,CommandType.Text,"select*from orders");reader.Close();
Main方法里,第一次调用Helper.ExecuteReader后建立了连接池并建立了一个连接,接着我们模拟连接进程被kill后再调用 Helper.ExecuteReader。为模拟连接进程被kill,先在master.dbo.sysprocesses查询 program_name为DeadConnectionExample(连接串的Application Name)的SPID,然后kill了该连接进程。当再次调用Helper.ExecuteReader的时候就遇到“死连接”(一定遇到,因为连接池里 只有一个连接)。用性能监视器观察连接池里的情况(先打开SQL Quary Analyzer得到一个User Connection以方便观测)得到图5。图5中连接池数量一直保持为1,因为kill连接进程所用的连接串没有使用了连接池。kill了连接进程后User Connections(蓝线)立刻下降1,而这时候连接池的连接数量(黄线)没有随着下降1,这就出现了一个“死连接”。接着,再从连接池取出连接访问 数据库的时候就抛出SqlException,这时候连接数量下降1,因为这时候Data Provider销毁“死连接”。接着,尝试使用其他连接,因为这时候连接池里连接数量为0,所以需要建立新连接,连接数量和User Connections同时上升1。为方便观测,在尝试其他连接前线程sleep了两秒。当然,如果“死连接”是由于网络中断、数据库被shutdown引起,那么Helper只能最后抛出SqlException。注意:查询master.dbo.sysprocesses使用的连接串没有必要使用连接池。<system.diagnostics> <switches> <add name="ConnectionPoolPerformanceCounterDetail"value="4"/></switches> </system.diagnostics>
NumberOfActiveConnectionPoolGroups计数器。前面说过,如果连接串使用Windows认证,那么不同的Windows 用户有不同的连接池,ADO.NET 2.0中使用NumberOfActiveConnectionPoolGroups把使用Windows认证的相同连接串(字符相同)产生的不同连接池 归为一组。NumberOfActiveConnections, NumberOfFreeConnections计数器。ADO.NET 1.1里的计数器没有提供一个连接池里的连接有多少个是“被占用的”,有多少个是“可用的”。NumberOfActiveConnections和 NumberOfFreeConnections填补了这个空白。这两个计数器更加“生动”地描述了连接池里连接的变化情况。图6是一个连接相继 Open/Close了4次得到的比ADO.NET 1.1更“生动”的曲线。图6    7、总结明白了连接池的运作机制不等于能正确使用连接池,要充分挖掘连接池给应用系统带来的性能提高,除了避免泄漏连接需要注意的两点外, 请参考一下建议:(1)确保每次访问数据库使用相同的连接串,连接串不要使用Windows认证。(2)到了非打开不可的时候才打开连接,连接使用完毕立刻关闭连接。因为过早占用和过晚释放连接意味着增加连接池的不必要负荷(需要建立更多的连接以及连接请求需要等待更长时间)。(3) 根据应用系统的实际负荷设置适当的Min Pool Size和Max Pool Size。为避免连接请求超时,如果应用系统的数据库最大并发访问数量大于Max Pool Size的默认置100就需要把Max Pool Size设置得更大;但不是越大越好,毕竟数据库的负荷承受力有限。如果应用系统的数据库最大并非访问数量是N,那么Min Pool Size不要大于N。(4)如果应用系统不是使用集群数据库,把Connection Lifetime设置为0。在单数据库服务器的环境下没必要把连接销毁,因为销毁后一段时间又需要建立。连接池对应用系统的性能提高起着至关重要的作用,但需要连接池有其适用范围,它适用于需要频繁访问数据库的应用系统。对于低频率(例如一天只有几次)的数据库访问应用系统就不必要,因为一直保留一个低使用频率的“物理连接”不如使用一次就建立一次好。
                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐