连接数计算公式: ​连接数 = ((核心数 * 2) + 有效磁盘数)

前言

当我们在寻找数据库的性能瓶颈时,大致可归为三类(内存的确是需要考虑的,但是比起​磁盘IO​和​网络IO​,稍显微不足道):​CPU、磁盘 IO、网络 IO​。

假设我们不考虑磁盘 IO 和网络 IO,就很好定论了,在一个 8 核的服务器上,数据库连接数/线程数设置为 8 能够提供最优的性能,如果再增加连接数,反而会因为上下文切换导致性能下降。

大家都知道,数据库通常把数据存储在磁盘上,而磁盘呢,通常是由一些旋转着的金属碟片和一个装在步进马达上的读写头组成的。读/写头同一时刻只能出现在一个位置,当它需要再次执行读写操作时,它必须“寻址”到另外一个位置才能完成任务。所以呢?这里就有了​寻址的耗时​,此外还有​旋转耗时​,读写头需要等待磁盘碟片上的目标数据“旋转到位”才能进行读写操作。使用缓存当然是能够提升性能的,但上述原理仍然适用。在这段("I/O等待")时间内,线程是处于“阻塞”等待状态,也就是说没干啥正事!此时操作系统可以将这个空闲的CPU 核心用于服务其他线程。当你的线程处理的是 I/O 密集型业务时,便可以让线程/连接数设置的比 CPU核心大一些,这样就能够在同样的时间内,完成更多的工作,提升吞吐量。

大小设置成多少合适呢?

这要取决于​磁盘​,如果你使用的是 SSD 固态硬盘,它不需要寻址,也不需要旋转碟片。打住打住!!!你千万可别理所当然的认为:“​既然SSD速度更快,我们把线程数的大小设置的大些吧!!​”结论正好相反!无需寻址和没有旋回耗时的确意味着​更少的阻塞​,所以更少的线程(更接近于CPU核心数)会发挥出更高的性能。只有当阻塞密集时,更多的线程数才能发挥出更好的性能。

网络 IO 其实也是非常相似的。通过以太网接口读写数据时也会造成阻塞,10G带宽会比1G带宽的阻塞耗时少一些,而 1G 带宽又会比 100M 带宽的阻塞少一些。通常情况下,我们把网络 IO 放在第三顺位来考虑,然而有些人会在性能计算中忽略网络 IO 带来的影响。

数据库连接池的大小_连接池

上图是 PostgreSQL 的基准性能测试数据,从图中可看到,TPS 在连接数达到 50 时开始变缓。回过头来想下,在上面 Oracle 的性能测试视频中,测试人员们将连接数从 2048 降到了 96,实际上 96 还是太高了,除非你的服务器 CPU 核心数有 16 或 32。

连接数计算公式

下面公式由 PostgreSQL 提供,不过底层原理是不变的,它适用于市面上绝大部分数据库产品。还有,你应该模拟预期的访问量,并通过下面的公式先设置一个偏合理的值,然后在实际的测试中,通过微调,来寻找最合适的连接数大小。

连接数 = ((核心数 * 2) + 有效磁盘数)


核心数不应包含超线程(hyper thread),即使打开了超线程也是如此,如果热点数据全被缓存了,那么有效磁盘数实际是0,随着缓存命中率的下降,有效磁盘数也逐渐趋近于实际的磁盘数。另外需要注意,这一公式作用于SSD 的效果如何,尚未明了。


如果说你的服务器 CPU 是 4核 i7 的,连接池大小应该为 ​​((4*2)+1)=9​​。取个整, 我们就设置为 10 吧。可以跑个性能测试看看,它能轻松支撑 3000 用户以 6000 TPS 的速率并发执行简单查询的场景。你还可以将连接池大小超过 10,那时,你会看到响应时长开始增加,TPS 开始下降。

结论:你需要的是一个小连接池,和一个等待连接的线程队列

假设说你有 10000 个并发访问,而你设置了连接池大小为 10000,你怕是石乐志哦。改成 1000,太高?改成 100?还是太多了。你仅仅需要一个大小为 10 数据库连接池,然后让剩下的业务线程都在队列里等待就可以了。


连接池中的连接数量大小应该设置成:数据库能够有效同时进行的查询任务数(通常情况下来说不会高于 2*CPU核心数)。


你应该经常会看到一些用户量不是很大的 web 应用中,为应付大约十来个的并发,却将数据库连接池设置成 100, 200 的情况。请不要过度配置您的数据库连接池的大小。

注意点

实际上,连接池的大小的设置还是要结合实际的业务场景来说事。比如说,你的系统同时混合了​长事务​和​短事务​,这时,根据上面的公式来计算就很难办了。正确的做法应该是创建两个连接池,一个服务于长事务,一个服务于"实时"查询,也就是短事务。

还有一种情况,比方说一个系统执行一个任务队列,业务上要求同一时间内只允许执行一定数量的任务,这时,我们就应该让并发任务数去适配连接池连接数,而不是连接数大小去适配并发任务数。