数据库连接池到底应该设多大?这篇文章可能会颠覆你的认知

数据库连接池的配置是开发者们常常搞出坑的地方,在配置数据库连接池时,有几个可以说是和直觉背道而驰的原则需要明确 。
1万并发用户访问想象你有一个网站,压力虽然还没到Facebook那个级别,但也有个1万上下的并发访问——也就是说差不多2万左右的TPS 。那么这个网站的数据库连接池应该设置成多大呢?结果可能会让你惊讶,因为这个问题的正确问法是:
“这个网站的数据库连接池应该设置成多小呢?”

下面这个视频是Oracle Real World Performance Group发布的,请先看完:http://www.dailymotion.com/video/x2s8uec
因为这视频是英文解说且没有字幕,我替大家做一下简单的概括:
视频中对Oracle数据库进行压力测试,9600并发线程进行数据库操作,每两次访问数据库的操作之间sleep 550ms,一开始设置的中间件线程池大小为2048:
数据库连接池到底应该设多大?这篇文章可能会颠覆你的认知

文章插图
 
压测跑起来之后是这个样子的:
数据库连接池到底应该设多大?这篇文章可能会颠覆你的认知

文章插图
 
【数据库连接池到底应该设多大?这篇文章可能会颠覆你的认知】每个请求要在连接池队列里等待33ms,获得连接后执行SQL需要77ms
此时数据库的等待事件是这个熊样的:
数据库连接池到底应该设多大?这篇文章可能会颠覆你的认知

文章插图
 
各种buffer busy waits,数据库CPU在95%左右(这张图里没截到CPU)
接下来,把中间件连接池减到1024(并发什么的都不变),性能数据变成了这样:
数据库连接池到底应该设多大?这篇文章可能会颠覆你的认知

文章插图
 
获取链接等待时长没怎么变,但是执行SQL的耗时减少了 。
下面这张图,上半部分是wait,下半部分是吞吐量
数据库连接池到底应该设多大?这篇文章可能会颠覆你的认知

文章插图
 
能看到,中间件连接池从2048减半之后,吐吞量没变,但wait事件减少了一半 。
接下来,把数据库连接池减到96,并发线程数仍然是9600不变 。
数据库连接池到底应该设多大?这篇文章可能会颠覆你的认知

文章插图
 
队列平均等待1ms,执行SQL平均耗时2ms 。
数据库连接池到底应该设多大?这篇文章可能会颠覆你的认知

文章插图
 
wait事件几乎没了,吞吐量上升 。
没有调整任何其他东西,仅仅只是缩小了中间件层的数据库连接池,就把请求响应时间从100ms左右缩短到了3ms 。
But why?为什么Nginx只用4个线程发挥出的性能就大大超越了100个进程的Apache HTTPD?回想一下计算机科学的基础知识,答案其实是很明显的 。
即使是单核CPU的计算机也能“同时”运行数百个线程 。但我们都[应该]知道这只不过是操作系统用时间分片玩的一个小把戏 。一颗CPU核心同一时刻只能执行一个线程,然后操作系统切换上下文,核心开始执行另一个线程的代码,以此类推 。给定一颗CPU核心,其顺序执行A和B永远比通过时间分片“同时”执行A和B要快,这是一条计算机科学的基本法则 。一旦线程的数量超过了CPU核心的数量,再增加线程数系统就只会更慢,而不是更快 。推荐:多线程内容聚合
这几乎就是真理了……
有限的资源上面的说法只能说是接近真理,但还并没有这么简单,有一些其他的因素需要加入 。当我们寻找数据库的性能瓶颈时,总是可以将其归为三类:CPU、磁盘、网络 。把内存加进来也没有错,但比起磁盘和网络,内存的带宽要高出好几个数量级,所以就先不加了 。
如果我们无视磁盘和网络,那么结论就非常简单 。在一个8核的服务器上,设定连接/线程数为8能够提供最优的性能,再增加连接数就会因上下文切换的损耗导致性能下降 。数据库通常把数据存储在磁盘上,磁盘又通常是由一些旋转着的金属碟片和一个装在步进马达上的读写头组成的 。
读/写头同一时刻只能出现在一个地方,然后它必须“寻址”到另外一个位置来执行另一次读写操作 。所以就有了寻址的耗时,此外还有旋回耗时,读写头需要等待碟片上的目标数据“旋转到位”才能进行操作 。使用缓存当然是能够提升性能的,但上述原理仍然成立 。
在这一时间段(即"I/O等待")内,线程是在“阻塞”着等待磁盘,此时操作系统可以将那个空闲的CPU核心用于服务其他线程 。所以,由于线程总是在I/O上阻塞,我们可以让线程/连接数比CPU核心多一些,这样能够在同样的时间内完成更多的工作 。


推荐阅读