技术开发 频道

ADO.NET数据连接池

【IT168 技术文档】

    4、连接泄漏

    前面说过,连接被打开后需要执行Close或者Dispose方法后才会释放回连接池。如果一个连接已经离开其代码有效范围,但还没被Close或者Dispose,该连接就被泄漏了。所谓泄漏的连接就是代码中已经不再使用某个连接但该连接却还没有被释放回连接池。下面代码中,每执行一次Method()就泄漏一个连接,第11次执行的时候就会抛出InvalidOperationException,因为最大连接数已达而且所有连接都已经被占用。

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.sysprocesses
WHERE 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使用的连接串没有必要使用连接池。 

图5

0
相关文章