在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
正确的理解这个连接池机制,有助于我们编写高效的数据库应用程序。 很多人认为 SqlConnection 的连接是不耗时的,理由是循环执行 SqlConnection.Open 得到的平均时间几乎为0,但每次首次open 时,耗时又往往达到几个毫秒到几秒不等,这又是为什么呢? 首先我们看一下 MSDN 上的权威文档上是怎么说的 Connecting to a database server typically consists of several time-consuming steps. A physical channel such as a socket or a named pipe must be established, the initial handshake with the server must occur, the connection string information must be parsed, the connection must be authenticated by the server, checks must be run for enlisting in the current transaction, and so on. 以上摘自 http://msdn.microsoft.com/en-us/library/8xx3tyca%28VS.80%29.aspx 也就是说物理连接建立时,需要做和服务器握手,解析连接字符串,授权,约束的检查等等操作,而物理连接建立后,这些操作就不会去做了。这些操作是需要一定的时间的。所以很多人喜欢用一个静态对象存储 SqlConnection 来始终保持物理连接,但采用静态对象时,多线程访问会带来一些问题,实际上,我们完全不需要这么做,因为 SqlConnection 默认打开了连接池功能,当程序 执行 SqlConnection.Close 后,物理连接并不会被立即释放,所以这才出现当循环执行 Open操作时,执行时间几乎为0. 下面我们先看一下不打开连接池时,循环执行 SqlConnection.Open 的耗时 复制代码 代码如下: public static void OpenWithoutPooling() { string connectionString = "Data Source=192.168.10.2; Initial Catalog=News; Integrated Security=True;Pooling=False;"; Stopwatch sw = new Stopwatch(); sw.Start(); using (SqlConnection conn = new SqlConnection(connectionString)) { conn.Open(); } sw.Stop(); Console.WriteLine("Without Pooling, first connection elapsed {0} ms", sw.ElapsedMilliseconds); sw.Reset(); sw.Start(); for (int i = 0; i < 100; i++) { using (SqlConnection conn = new SqlConnection(connectionString)) { conn.Open(); } } sw.Stop(); Console.WriteLine("Without Pooling, average connection elapsed {0} ms", sw.ElapsedMilliseconds / 100); } 复制代码 代码如下: .csharpcode { BACKGROUND-COLOR: #ffffff; FONT-FAMILY: consolas, "Courier New", courier, monospace; COLOR: black; FONT-SIZE: small } .csharpcode PRE { BACKGROUND-COLOR: #ffffff; FONT-FAMILY: consolas, "Courier New", courier, monospace; COLOR: black; FONT-SIZE: small } .csharpcode PRE { MARGIN: 0em } .csharpcode .rem { COLOR: #008000 } .csharpcode .kwrd { COLOR: #0000ff } .csharpcode .str { COLOR: #006080 } .csharpcode .op { COLOR: #0000c0 } .csharpcode.preproc { COLOR: #cc6633 } .csharpcode .asp { BACKGROUND-COLOR: #ffff00 } .csharpcode .html { COLOR: #800000 } .csharpcode .attr { COLOR: #ff0000 } .csharpcode .alt { BACKGROUND-COLOR: #f4f4f4; MARGIN: 0em; WIDTH: 100% } .csharpcode .lnum { COLOR: #606060 } SqlConnection 默认是打开连接池的,如果要强制关闭,我们需要在连接字符串中加入 Pooling=False 调用程序如下: 复制代码 代码如下: Test.SqlConnectionTest.OpenWithoutPooling(); Console.WriteLine("Waiting for 10s"); System.Threading.Thread.Sleep(10 * 1000); Test.SqlConnectionTest.OpenWithoutPooling(); Console.WriteLine("Waiting for 600s"); System.Threading.Thread.Sleep(600 * 1000); Test.SqlConnectionTest.OpenWithoutPooling(); 下面是测试结果 复制代码 代码如下: Without Pooling, first connection elapsed 13 ms Without Pooling, average connection elapsed 5 ms Wating for 10s Without Pooling, first connection elapsed 6 ms Without Pooling, average connection elapsed 4 ms Wating for 600s Without Pooling, first connection elapsed 7 ms Without Pooling, average connection elapsed 4 ms 从这个测试结果看,关闭连接池后,平均每次连接大概要耗时4个毫秒左右,这个就是建立物理连接的平均耗时。 下面再看默认情况下的测试代码 复制代码 代码如下: public static void OpenWithPooling() { string connectionString = "Data Source=192.168.10.2; Initial Catalog=News; Integrated Security=True;"; Stopwatch sw = new Stopwatch(); sw.Start(); using (SqlConnection conn = new SqlConnection(connectionString)) { conn.Open(); } sw.Stop(); Console.WriteLine("With Pooling, first connection elapsed {0} ms", sw.ElapsedMilliseconds); sw.Reset(); sw.Start(); for (int i = 0; i < 100; i++) { using (SqlConnection conn = new SqlConnection(connectionString)) { conn.Open(); } } sw.Stop(); Console.WriteLine("With Pooling, average connection elapsed {0} ms", sw.ElapsedMilliseconds / 100); } 调用代码 复制代码 代码如下: Test.SqlConnectionTest.OpenWithPooling(); Console.WriteLine("Waiting for 10s"); System.Threading.Thread.Sleep(10 * 1000); Test.SqlConnectionTest.OpenWithPooling(); Console.WriteLine("Waiting for 600s"); System.Threading.Thread.Sleep(600 * 1000); Test.SqlConnectionTest.OpenWithPooling(); 测试结果 With Pooling, first connection elapsed 119 ms
但我们发现一个有趣的现象,10分钟后,首次连接时间变成了6ms,这个和前面不打开连接池的测试用时几乎一样,也就是说10分钟后,物理连接被关闭了,又重新打开了一个物理连接。这个现象是因为连接池有个超时时间,默认情况下应该在5-10分钟之间,如果在此期间没有任何的连接操作,物理连接就会被关闭。那么我们有没有办法始终保持物理连接呢?方法是有的。 连接池设置中有一个最小连接池大小,默认为0,我们把它设置为大于0的值就可以保持若干物理连接始终不释放了。看代码 复制代码 代码如下: public static void OpenWithPooling(int minPoolSize) { string connectionString = string.Format("Data Source=192.168.10.2; Initial Catalog=News; Integrated Security=True;Min Pool Size={0}",minPoolSize); Stopwatch sw = new Stopwatch(); sw.Start(); using (SqlConnection conn = new SqlConnection(connectionString)) { conn.Open(); } sw.Stop(); Console.WriteLine("With Pooling Min Pool Size={0}, first connection elapsed {1} ms",minPoolSize, sw.ElapsedMilliseconds); sw.Reset(); sw.Start(); for (int i = 0; i < 100; i++) { using (SqlConnection conn = new SqlConnection(connectionString)) { conn.Open(); } } sw.Stop(); Console.WriteLine("With Pooling Min Pool Size={0}, average connection elapsed {1} ms",minPoolSize, sw.ElapsedMilliseconds / 100); } 其实只要在连接字符串中加入一个 Min Pool Size=n 就可以了。 调用代码 复制代码 代码如下: Test.SqlConnectionTest.OpenWithPooling(1); Console.WriteLine("Waiting for 10s"); System.Threading.Thread.Sleep(10 * 1000); Test.SqlConnectionTest.OpenWithPooling(1); Console.WriteLine("Waiting for 600s"); System.Threading.Thread.Sleep(600 * 1000); Test.SqlConnectionTest.OpenWithPooling(1); With Pooling Min Pool Size=1, first connection elapsed 5 ms With Pooling Min Pool Size=1, average connection elapsed 0 ms Waiting for 10s With Pooling Min Pool Size=1, first connection elapsed 0 ms With Pooling Min Pool Size=1, average connection elapsed 0 ms Waiting for 600s With Pooling Min Pool Size=1, first connection elapsed 0 ms With Pooling Min Pool Size=1, average connection elapsed 0 ms
多线程调用问题 那么这里有两个问题,如果后一个线程在前一个线程 Close 前调用了Open操作,那么 Ado.net 不可能复用一个物理连接,它将为第二个线程分配一个新的物理连接。如果后一个线程 Open 时,前一个线程已经 Close 了,则新的线程使用前一个线程的物理连接。也就是说,如果同时有n个线程连接数据库,最多情况下会创建n条物理连接,最少情况下为1条。如果创建n条物理连接,则用时理论上等于 n * t / cpu , n 为线程数,t 为每次创建物理连接的用时,前面测试的结果大概是5-10ms左右,cpu 为当前机器的CPU数量。另外网络,服务器的负荷也影响这个用时。为了保证在大并发时,尽量少的创建新的物理连接,我们可以适当把 Min Pool Size 调大一些,但也不要太大,因为单个机器TCP链路的数量是有限的,详见我另外一篇文章 Windows 下单机最大TCP连接数 连接字符串中关于 连接池方面的参数 见下面链接 SqlConnection.ConnectionString Property
IIS 回收应用程序池对连接池的影响 |
请发表评论