音效素材网提供各类素材,打造精品素材网站!

站内导航 站长工具 投稿中心 手机访问

音效素材

.net 中的SqlConnection连接池机制详解
日期:2021-09-07 21:39:50   来源:脚本之家

正确的理解这个连接池机制,有助于我们编写高效的数据库应用程序。

很多人认为 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
With Pooling, average connection elapsed 0 ms
Waiting for 10s
With Pooling, first connection elapsed 0 ms
With Pooling, average connection elapsed 0 ms
Waiting for 600s
With Pooling, first connection elapsed 6 ms
With Pooling, average connection elapsed 0 ms


这个测试结果看,第一次耗时是119ms,这是因为我在测试代码中,首先运行的是这个测试过程,119 ms 是程序第一次启动时的首次连接耗时,这个耗时可能不光包括连接数据库的时间,还有 ado.net 自己初始化的用时,所以这个用时可以不管。10秒以后在执行这个测试过程,首次执行的时间变成了0ms,这说明连接池机制发生了作用,SqlConnection Close 后,物理连接并没有被关闭,所以10秒后再执行,连接几乎没有用时间。

但我们发现一个有趣的现象,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


我们可以看到当 Min Pool Size = 1  时,除了首次连接用时5ms以外,即便过了10分钟,用时还是0ms,物理连接没有被关闭。

 

多线程调用问题
多线程调用我也做了测试,这里不贴代码了,我大概讲一下结果。如果是多线程访问 SqlConnection ,注意是通过 new SqlConnection 方式访问,

那么这里有两个问题,如果后一个线程在前一个线程 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 回收应用程序池对连接池的影响
在做 ASP.NET 程序时,我们会发现,如果网站20分钟不访问,再次访问就会比较慢,这是因为IIS默认的 idle timeout 是20分钟,如果在20分钟内没有一个访问,IIS 将回收应用程序池,回收应用程序池的结果就相当于应用程序被重启,所有原来的全局变量,session, 物理连接都将清空。回收应用程序池后首次访问,相当于前面我们看到的程序启动后第一次访问数据库,连接的建立时间将比较长。所以如果网站在某些时段访问量很少的话,需要考虑 idle timeout 是否设置合理。

    您感兴趣的教程

    在docker中安装mysql详解

    本篇文章主要介绍了在docker中安装mysql详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编...

    详解 安装 docker mysql

    win10中文输入法仅在桌面显示怎么办?

    win10中文输入法仅在桌面显示怎么办?

    win10系统使用搜狗,QQ输入法只有在显示桌面的时候才出来,在使用其他程序输入框里面却只能输入字母数字,win10中...

    win10 中文输入法

    一分钟掌握linux系统目录结构

    这篇文章主要介绍了linux系统目录结构,通过结构图和多张表格了解linux系统目录结构,感兴趣的小伙伴们可以参考一...

    结构 目录 系统 linux

    PHP程序员玩转Linux系列 Linux和Windows安装

    这篇文章主要为大家详细介绍了PHP程序员玩转Linux系列文章,Linux和Windows安装nginx教程,具有一定的参考价值,感兴趣...

    玩转 程序员 安装 系列 PHP

    win10怎么安装杜比音效Doby V4.1 win10安装杜

    第四代杜比®家庭影院®技术包含了一整套协同工作的技术,让PC 发出清晰的环绕声同时第四代杜比家庭影院技术...

    win10杜比音效

    纯CSS实现iOS风格打开关闭选择框功能

    这篇文章主要介绍了纯CSS实现iOS风格打开关闭选择框,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作...

    css ios c

    Win7如何给C盘扩容 Win7系统电脑C盘扩容的办法

    Win7如何给C盘扩容 Win7系统电脑C盘扩容的

    Win7给电脑C盘扩容的办法大家知道吗?当系统分区C盘空间不足时,就需要给它扩容了,如果不管,C盘没有足够的空间...

    Win7 C盘 扩容

    百度推广竞品词的投放策略

    SEM是基于关键词搜索的营销活动。作为推广人员,我们所做的工作,就是打理成千上万的关键词,关注它们的质量度...

    百度推广 竞品词

    Visual Studio Code(vscode) git的使用教程

    这篇文章主要介绍了详解Visual Studio Code(vscode) git的使用,小编觉得挺不错的,现在分享给大家,也给大家做个参考。...

    教程 Studio Visual Code git

    七牛云储存创始人分享七牛的创立故事与

    这篇文章主要介绍了七牛云储存创始人分享七牛的创立故事与对Go语言的应用,七牛选用Go语言这门新兴的编程语言进行...

    七牛 Go语言

    Win10预览版Mobile 10547即将发布 9月19日上午

    微软副总裁Gabriel Aul的Twitter透露了 Win10 Mobile预览版10536即将发布,他表示该版本已进入内部慢速版阶段,发布时间目...

    Win10 预览版

    HTML标签meta总结,HTML5 head meta 属性整理

    移动前端开发中添加一些webkit专属的HTML5头部标签,帮助浏览器更好解析HTML代码,更好地将移动web前端页面表现出来...

    移动端html5模拟长按事件的实现方法

    这篇文章主要介绍了移动端html5模拟长按事件的实现方法的相关资料,小编觉得挺不错的,现在分享给大家,也给大家...

    移动端 html5 长按

    HTML常用meta大全(推荐)

    这篇文章主要介绍了HTML常用meta大全(推荐),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参...

    cdr怎么把图片转换成位图? cdr图片转换为位图的教程

    cdr怎么把图片转换成位图? cdr图片转换为

    cdr怎么把图片转换成位图?cdr中插入的图片想要转换成位图,该怎么转换呢?下面我们就来看看cdr图片转换为位图的...

    cdr 图片 位图

    win10系统怎么录屏?win10系统自带录屏详细教程

    win10系统怎么录屏?win10系统自带录屏详细

    当我们是使用win10系统的时候,想要录制电脑上的画面,这时候有人会想到下个第三方软件,其实可以用电脑上的自带...

    win10 系统自带录屏 详细教程

    + 更多教程 +
    ASP编程JSP编程PHP编程.NET编程python编程