数据库连接池的设计细节以及一个坑

Posted 听啃先生说H5

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据库连接池的设计细节以及一个坑相关的知识,希望对你有一定的参考价值。

去年新搭建的 Web 服务,有用户反馈偶现页面打开延迟较长的情况,最初以为是用户网络问题。后来通过完善各环节日志,找到某些复现的规律

  • 例如凌晨时打开延迟较大

  • 程序没有抛出异常

分析各环节,最后定位在数据库连接池的设计存在缺陷。所以记录一下数据库连接池的一般实现,以及 Node.js mysql 模块连接池存在的缺陷。


一、数据库连接池设计

通过 npm install mysql 安装的 mysql 模块,包含了连接池的实现。它的设计与其他数据库驱动(比如 c 语言)类似。


  1. 初始化连接池。即创建一个空的容量有限的连接池,可理解为一个长度确定的空数组

  2. 当执行 SQL 语句的任务到来时,选出一个可用的连接对象。

  3. a. 判断连接池中是否有连接对象,有则下一步,无则创建 socket 连接对象放入池中,并使用此对象
    b. 遍历连接池中的连接对象,挑选出空闲的对象,若存在,则使用该对象;若不存在,则下一步
    c. 判断连接池中连接对象数量是否达到上限,若达到,则任务进入等待队列,等待正在执行 SQL 语句的对象空闲;若未达到,则创建 socket 连接对象放入池中,并使用此对象
    d. 将以上步骤得到的连接对象用来发送一个 ping 指令给 MySQL 服务器,以确保该连接可用。若 ping 失败,丢弃此连接对象,回到 a 步骤,重新开始,直到得到一个可用的连接对象。
  4. SQL 语句执行过程中,执行该语句的连接对象被标志为非空闲状态

  5. SQL 语句执行完毕,将连接对象释放,该对象变成空闲状态。

  6. 判断等待队列是否为空,空则等待新的执行任务。若非空,则表明还有 SQL 语句未执行,则重新进入第 2 个步骤,继续执行任务。


以上步骤有一个细节需要注意:任务等待队列也有容量限制,当等待执行的语句太多时,新的执行任务直接会被丢弃,并抛出执行失败的错误。



二、 Node.js mysql 模块连接池的设计缺陷


连接池在初始化时,会设置一些属性,其中包含一个获取连接对象的超时时间(acquireTimeout),这是上述 2.d 步骤中 ping 的超时时间。


当一个 socket 连接对象长时间空闲(访问量太少,没有需要执行的 SQL),这个 socket 本身可能已经损坏不可用,所以从连接池中获得的连接对象在使用前,需要 ping 一下 MySQL,确认可用再拿来执行 SQL 语句。ping 本身的通信量非常少,只是一个信号,所以成功通常非常快,几毫秒可能结束了。而失败则通常通过超时来判定。


回到本文开头的问题 —— 为什么会偶现延迟,且程序没有抛出异常?

  • 网站刚上线访问量小,socket 连接对象长时间空闲已损坏,而 acquireTimeout 默认时间为10秒,ping 要等待超时后才去获取其它连接

  • 根据上一节所述步骤,ping超时后,驱动程序会丢弃此损坏的连接,去获取新连接,并在新连接上执行 SQL,只要最终执行成功,就不会认为出错,只是时间稍长


三、解决办法

具体情况,具体分析,以下列出三个解决办法供参考:

  1. 初始化连接池时,设置合理的属性

    1. acquireTimeout 设置为比较短的时间,比如正常的机房1秒足够了,如果超过1秒都不能拼通,可以认为机房有问题

    2. 假设连接池中的对象都已损坏,那连接池有N个连接,则理论上最大的延迟是 acquireTimeout的N倍,那么延迟还是有可能很大。所以将连接池大小设置为1,即只有一个连接的连接池。

  2. 连接池里的每个连接定时 ping 一下 MySQL 服务器,不要让它闲

  3. 或者干脆不要使用连接池好了,执行任务前获取连接,执行结束销毁连接

  4. 将业务发展起来,不要空闲,当然这不是技术问题了






以上是关于数据库连接池的设计细节以及一个坑的主要内容,如果未能解决你的问题,请参考以下文章

连接池的基本原理? 以及使用连接池的好处?

was连接池怎么看

swoole 4.x 连接池协程版本细节坑

设计一个可靠的连接池

redis默认连接池的这个坑,你真的知道吗?

数据库连接池的工作原理以及这项技术的产生