关于SQL 数据库连接池 的使用问题。

shoppo0505 2019-02-13 02:28:22
现在做项目养成了个习惯,做大项目的时候,数据库连接,使用连接池管理。小项目的时候,连接使用完就直接释放资源了。数据库连接池的优势也多少知道点。

这回感觉无聊,做了一下实际的数据库连接耗时对比。

连接方式1,使用连接池,运作方式如下:
创建SqlConnection, 打开数据库链接
for 1-100
{
创建时间点A
执行数据库操作,select 1
创建时间点B
显示操作时间。
}
释放SqlConnection

连接方式2,运作方式如下,数据库链接每次使用完之后直接释放:
for 1-100
{
创建时间点A
创建SqlConnection, 打开数据库链接
执行数据库操作,select 1
释放SqlConnection
创建时间点B
显示操作时间。
}

测试结果有点震惊,无论是连接本地,还是本国外地,还是外国数据库,这两种操作的时间可以说是一样的。没有任何区别。
那问题来了,连接池的优势到底是什么呢?什么时候能明显提升性能呢?

...全文
1094 35 打赏 收藏 转发到动态 举报
写回复
用AI写文章
35 条回复
切换为时间正序
请发表友善的回复…
发表回复
  • 打赏
  • 举报
回复
这里,最关键地是“开窍”,能看到真正为什么那样编程。 这个问题并不是“性能”问题。之所以要在使用到数据库连接时及时地创建逻辑连接,用完了就要及时地释放连接(确保在 using 结构中自动释放),是因为对数据库频繁访问的过程存在有大量的并发(例如通讯服务端每一个消息处理)、嵌套过程。
  • 打赏
  • 举报
回复
连接池实际上是 ADO.NET 的针对不同数据库引擎的实现机制来实现的,并不是说凡是 ADO.NET(凡是有连接串的)就一定支持连接池。但是 SqlDbConnection、OleDbConnecton 等等的 DbConnection 子类实现代码起码是支持连接池的,这个是文档中明确说明过的。 在编写代码中,谁会在第二种代码中去刻意一遍遍地创建新的连接对象?没有必要创建新对象的时候就不应该创建新对象。甚至说没有必要声明一个 Class 的时候我们都不应该随便声明 Class,否则就越来越复杂、越来越乱。我们之所以要声明class、interface 等等,之所以要面向对象设计,是因为在封装、职责、扩展等方面确实是极大地简化了设计。那么这种“简化”对于初学者就是不理解的、相反的,只有当他真正发现了必要性的时候才需要。所以没有必要性的时候我肯定没有几个人会说什么“必须创建连接对象”,而当你连功能正确性都无法保证的时候自然也就不去纠结什么“消耗资源”问题了。
  • 打赏
  • 举报
回复
晕! 不论是 lz 的第一段程序还是第二段程序,都使用到了连接池,所以对于连接池的“测试结果”结论一样。SqlDbConnection 是自动管理连接池的,不是你写什么代码来自己发明连接池的。 这两段代码的区别不过是是否反反复复地初始化连接对象的区别。跟连接池根本就无关。
LvBao_117 2019-02-17
  • 打赏
  • 举报
回复
将测试次数增加到如1000万,估计可以看到差别
LvBao_117 2019-02-17
  • 打赏
  • 举报
回复
首先,连接字符串默认开启了使用连接池

第二种应该消耗更多的计算机资源
close其实是关闭“应用程序自己内部”的连接(由ado.net管理的),而不是关闭和sql server的连接
第二种循环执行中,ado.net管理程序每次要判断是否将连接放回连接池,从而消耗资源
  • 打赏
  • 举报
回复
北京。不过目前长期在太原出差。
wanghui0380 2019-02-17
  • 打赏
  • 举报
回复
至于ado.net内部池到底有多大,对性能有什么影响,这个我不想分析他的源代码,我们来看另一个池bytePools,你就知道“池”化这种技术到底在我们的代码里,起的是什么作用

假设一个循环,每次分配 new byte[1024*1024*40] ,你觉着这个循环能分配多次?我相信很多人都会说,分不了多次,你的程序就的报错了“内存溢出”

假设一开始分配了一个 new byte[1024*1024*800]的池?然后我们每次循环是

ArraySegment<byte> bytes= pool.申请(024*1024*40)
//做我要做的事情
pool.归还(bytes)

那么这个循环可以做多少次。

ps:其实池化在普通net程序员里不是一个常规技术,微软自己把很多池化操作写在他微软的代码内部,所以你感觉不到。但是如果是经常处理并行/并发的NET程序员手上池化是常规技术(当然C++,java里一样,如果经常去nuget,git看那些比较有口碑的项目代码,你就会发现池化其实还并不算什么高级技术,实际上他很常见)
wanghui0380 2019-02-17
  • 打赏
  • 举报
回复
在回到你的问题。还是现实的例子,
共享单车的用法
1.我用完了,就会关锁归还
2.有时候,我骑上去了,但是中途会短暂离开-------比如我下车买包烟或者打张彩票,这时候虽然我离开了,但是并不表示我用完了,所以为了省掉开关锁的麻烦(或者怕被其他人起了),我选择不归还---因为我还压根没用完

这是我们个人的考虑,而在大局上共享单车更愿意大家高频率归还,因为他利用率高啊,吞吐量大。所以这个不是单纯说,个人方便与否。这个是在个人方便的同时,能否最大利用。

所以,结论是,不是XX园那种忽略上下文的说“A就是比B好,B千万别XXX”
从个人应用上一个中等低权重的长时间任务,应该时不时释放一下,让给高等高权重的短时间任务以总体平衡------这样的做法,你觉着呢?A和B都好,都有场合性用法,只是有时候A得让着B,B也得尽快完成免得堵着A
wanghui0380 2019-02-17
  • 打赏
  • 举报
回复
楼主的代码,可以说是大大的“误会”

你那个根本就不叫使用“池”,你只是说共享了一个已经从池里面取出来一个“联接”

我们说,什么叫“池”,池就是预先实例化好的一组实例,他是为了解决并发情况下,资源分配问题(尤其是内存分配问题)。打比方说,比如共享单车,人家是卖了100w辆车,然后你用了一辆,人家还了1辆,在一个时间范围内大家都有用的。
所以我们说,共享单车公司就是做了一个100w辆自行车的“自行车池”,大家共享了这个池。有人开锁骑车,有人关锁还车而已

所以,你说。你取了一辆车,然后你不还了。你每天都用这辆车,那么这车就成了你的“私车”,但是你说这才是“共享池”的真正用法,我们说很好,如果100w人都不还了,那么你就挂了。

其实老p跟你说的就是这个,微软在ado.net内部其实就已经池化了,而你只是从这个池化的东西里取了一个(当然和单车一样,你取了一辆车,然后用app开了个锁),但是请不要“骄傲”的说,我不还了(因为开关锁需要时间),我们只能说,当你“傲娇”的说不开关锁更省时间的同时,也意味着池里也永远少了一辆车(车的总数是一定的,一私有不还一个,那么他就少一个,少到池里没了,你的程序就异常把)
Lucy_zhou 2019-02-15
  • 打赏
  • 举报
回复
你好,怎么联系?有项目找开发合作。
  • 打赏
  • 举报
回复
例如说写一个方法
public void 增加工资(User[] array)
{
    using(var conn = 创建数据库连接())
    {
      foreach(var u in array)
      {
        保存数据库给u增加工资
      }
    }
}
这里当然就应该只创建一次连接,谁会去扯上在 foreach 里边去创建多个连接? 但是如果还反复纠结什么“应该在任意方法里使用全局连接”,这就说明它根本就不知道一个方法可能在任意的子线程中被调用,也不知道这个方法可能在别的 DbDataReader 的循环中被递归/迭代地调用。 不知道什么环境用什么编程方式,而只纠结毫无环境区分的死板结论,结论必定是混乱不堪非常幼稚。
shoppo0505 2019-02-15
  • 打赏
  • 举报
回复
引用 15 楼 吉普赛的歌 的回复:
[quote=引用 11 楼 shoppo0505 的回复:]
[quote=引用 8 楼 娃都会打酱油了 的回复:]
测试代码贴下呗

参考一楼的,基本一样。[/quote]

引用 12 楼 shoppo0505 的回复:
[quote=引用 6 楼 吉普赛的歌 的回复:]
楼主可以看一下我的博客: https://blog.csdn.net/yenange/article/details/79699547

你帖子里面的结论没有回答我的问题。[/quote]

你没有贴出自己的代码, 也没有看明白我的博客。

如果你真的是 #1 一样的代码, 那当然没有区别。

你要真正比较带连接池和不带连接池的, 就得设计两种不同的连接串, 一个带连接池, 一个不带连接池
#1的写法其实就是默认带了连接池, 两种测试是一样的, 只不过一个反复连接, 一个是长连接。反复连接看似低效,但因为有了连接池,连接得到重用, 所以基本没区别。

你把 #1 的连接串改成无连接池, 就会有差别了。[/quote]
恩,没看清楚

引用 18 楼 以专业开发人员为伍 的回复:
如果你想不到要在子线程中使用连接,也想不到一个过程中使用连接的时候(即使是同一个线程)在过程的宿主中可能已经占用了这个连接,那么可想而知你做过什么项目啊?如果你想不到我来告诉你,这就相当于你说你是建筑业的熟练工人,但是建筑工程师感觉你主要是参与过3层楼以下的小村子的民房,还不符合大项目的经验知识。

你离的越远越好
  • 打赏
  • 举报
回复
引用 13 楼 shoppo0505 的回复:
这个哪里有文字说明吗? 如果能够证实,那以后的话,直接采用第二种方法好了。 第一种方法,碰到过同步的问题。
就以你的代码来说,编程中就应该采用第一种方式,完全没有必要在 for 循环中去反复创建什么连接对象!这里的纠结性能也有必要。 你这类本末倒置了。所谓“同步问题”,起码不是你这种编程方式所体现的场景。而且到了那种场景而需要创建连接对象,那时候也就不会纠结你这个问题中所谓的性能问题,因为(在必须创建连接对象的情况下而使用坑爹的共享连接)连功能都保证不了了还纠结什么性能?! 所以说你这里设计了一个错误的前提,把真正实际的设计开发需求简化为初学者刚学数据库编程时的代码,标题党地歪曲了问题。
  • 打赏
  • 举报
回复
如果你想不到要在子线程中使用连接,也想不到一个过程中使用连接的时候(即使是同一个线程)在过程的宿主中可能已经占用了这个连接,那么可想而知你做过什么项目啊?如果你想不到我来告诉你,这就相当于你说你是建筑业的熟练工人,但是建筑工程师感觉你主要是参与过3层楼以下的小村子的民房,还不符合大项目的经验知识。
  • 打赏
  • 举报
回复
只有你只会写简单的小进程时才这样纠结。难道你不在子线程、异步操作中使用数据连接?难道你不频繁地在访问一个数据库连接的过程中、在 DbDataReader 已经独占了这个连接时去递归地/迭代地需要其它数据库连接?
shoppo0505 2019-02-15
  • 打赏
  • 举报
回复
引用 14 楼 闭包客 的回复:
[quote=引用 13 楼 shoppo0505 的回复:]
[quote=引用 10 楼 闭包客 的回复:]
简单地说,SqlConnection 的 Close 和 Dispose 方法,并不会立即关闭数据库连接,一般情况下,只是休眠。

这个哪里有文字说明吗?
如果能够证实,那以后的话,直接采用第二种方法好了。

第一种方法,碰到过同步的问题。[/quote]

https://docs.microsoft.com/en-us/dotnet/api/system.data.sqlclient.sqlconnection?redirectedfrom=MSDN&view=netframework-4.7.2
[/quote]
帖子有段话:
If the SqlConnection goes out of scope, it won't be closed. Therefore, you must explicitly close the connection by calling Close or Dispose. Close and Dispose are functionally equivalent. If the connection pooling value Pooling is set to true or yes, the underlying connection is returned back to the connection pool. On the other hand, if Pooling is set to false or no, the underlying connection to the server is actually closed.
然后查了一下Pooling 的默认值是true的。
所以推论就是,如果没有特意设定Pooling = false的话,数据库链接都会默认使用连接池。

对于这个推论,我又做了测试,测试结果如下:
测试1,本地后台+本地服务器,设定Pooling 分别为true/false:连接时间可以认为一样,1/100ms级没有明显差异
测试2,本地后台+国外服务器,设定Pooling 分别为true/false:使用连接池的,连接时间在24ms左右,但是不使用连接池的,240ms左右,差一个数量级。

至此,真想大白。感谢大家的回复,也解了我一个疑惑。

吉普赛的歌 2019-02-15
  • 打赏
  • 举报
回复
引用 11 楼 shoppo0505 的回复:
[quote=引用 8 楼 娃都会打酱油了 的回复:] 测试代码贴下呗
参考一楼的,基本一样。[/quote]
引用 12 楼 shoppo0505 的回复:
[quote=引用 6 楼 吉普赛的歌 的回复:] 楼主可以看一下我的博客: https://blog.csdn.net/yenange/article/details/79699547
你帖子里面的结论没有回答我的问题。[/quote] 你没有贴出自己的代码, 也没有看明白我的博客。 如果你真的是 #1 一样的代码, 那当然没有区别。 你要真正比较带连接池和不带连接池的, 就得设计两种不同的连接串, 一个带连接池, 一个不带连接池。 #1的写法其实就是默认带了连接池, 两种测试是一样的, 只不过一个反复连接, 一个是长连接。反复连接看似低效,但因为有了连接池,连接得到重用, 所以基本没区别。 你把 #1 的连接串改成无连接池, 就会有差别了。
闭包客 2019-02-15
  • 打赏
  • 举报
回复
引用 13 楼 shoppo0505 的回复:
[quote=引用 10 楼 闭包客 的回复:] 简单地说,SqlConnection 的 Close 和 Dispose 方法,并不会立即关闭数据库连接,一般情况下,只是休眠。
这个哪里有文字说明吗? 如果能够证实,那以后的话,直接采用第二种方法好了。 第一种方法,碰到过同步的问题。[/quote] https://docs.microsoft.com/en-us/dotnet/api/system.data.sqlclient.sqlconnection?redirectedfrom=MSDN&view=netframework-4.7.2
shoppo0505 2019-02-15
  • 打赏
  • 举报
回复
引用 10 楼 闭包客 的回复:
简单地说,SqlConnection 的 Close 和 Dispose 方法,并不会立即关闭数据库连接,一般情况下,只是休眠。

这个哪里有文字说明吗?
如果能够证实,那以后的话,直接采用第二种方法好了。

第一种方法,碰到过同步的问题。
shoppo0505 2019-02-15
  • 打赏
  • 举报
回复
引用 6 楼 吉普赛的歌 的回复:
楼主可以看一下我的博客: https://blog.csdn.net/yenange/article/details/79699547

你帖子里面的结论没有回答我的问题。
加载更多回复(15)

62,046

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术交流专区
javascript云原生 企业社区
社区管理员
  • ASP.NET
  • .Net开发者社区
  • R小R
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

.NET 社区是一个围绕开源 .NET 的开放、热情、创新、包容的技术社区。社区致力于为广大 .NET 爱好者提供一个良好的知识共享、协同互助的 .NET 技术交流环境。我们尊重不同意见,支持健康理性的辩论和互动,反对歧视和攻击。

希望和大家一起共同营造一个活跃、友好的社区氛围。

试试用AI创作助手写篇文章吧