MySQL 最佳实践:程序端连接池配置

时间:2022-07-26
本文章向大家介绍MySQL 最佳实践:程序端连接池配置,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

前言

什么是数据库连接池? 我们为什么需要它?

现代网站架构/业务架构越来越重视横向拓展的能力,随之而来的是 Server 或者容器的数量快速增长,但是传统 RDBMS 的扩展性无法跟上这种步伐,导致大量的数据库连接不断的在数据库端创建、断开,不仅性能方面受到影响,在个别极端情况下也会导致数据库本身出现卡死等影响业务的现象。

因此资源池的理念也被应用于数据库相关的场景,数据库连接池也应运而生。数据库连接池会提前创建,并维护一定数量的长连接,当程序端需要访问数据库时,连接池会“借”一个数据库连接出去,等使用完毕后再“还”给连接池。通过这种方式,避免了在数据库端大量的创建、断开数据库连接,不仅节省了数据库服务器的性能,还避开了业务高峰期对数据库产生的业务峰值。

连接池配置推荐

本章节会介绍一下连接池的常见问题,并列出几个主流的编程语言的连接池配置作为参考。本文可以结合 Oracle 的连接池配置的文章一起使用,来为业务定制合理的配置。

如何判断业务需要的总连接数

大多数业务都会使用容器或者其他的方式部署多个业务端,来使用同一个数据库实例,那么设置数据库端的连接数限制时,就需要设置成最大可能的连接数。

假设业务部署时使用了 N 个容器(例如 Docker),每个容器设置的连接数硬上限为 40,那么在数据库端至少要把最大连接数设置为 N*40,一般为了安全起见会把这个最大连接数设置为 N*40+100。有一部分业务的客户端没有连接池,而是用 processor,worker,thread 等方式来设置工作、并发线程数,那么这些客户端可能是使用短连接来连接数据库,最大连接数应该设置为 N*max_processor+100,N*max_worker+100 等。

实际已使用的连接数,可以在具体的数据库端进行查看,以 MySQL 为例,执行 show global status like '%Threads_connected%'; 进行查看。

Java

c3p0 是 Java 中较常用的连接池,详细配置信息参考文档,多数情况下可以参考如下配置。

<!-- c3p0连接池配置 -->
     <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
          <!-- 用户名 -->
          <property name="user" value="${username}"/>
          <!-- 用户密码 -->
          <property name="password" value="${password}"/>
          <property name="driverClass" value="${driver_class}"/>
          <property name="jdbcUrl" value="${url}"/>
 
           <!--连接池中保留的最大连接数,硬性限制,不会超过这个值。默认值: 15 --> 
          <property name="maxPoolSize" value="40"/>
          <!-- 连接池中保留的最小连接数,默认为:3 -->
          <property name="minPoolSize" value="20"/>
          <!-- 初始化连接池中的连接数,取值应在minPoolSize与maxPoolSize之间,默认为3 -->
          <property name="initialPoolSize" value="20"/>
 
          <!--最大空闲时间,60秒内未使用则连接被丢弃。若为0则永不丢弃。默认值: 0 --> 
          <property name="maxIdleTime">3600</property>
          
          <!-- 当连接池连接耗尽时,客户端调用getConnection()后等待获取新连接的时间,超时后将抛出SQLException,如设为0则无限期等待。单位毫秒。默认: 0 --> 
          <property name="checkoutTimeout" value="3000"/>
          
          <!--当连接池中的连接耗尽的时候c3p0一次同时获取的连接数。默认值: 3 --> 
          <property name="acquireIncrement" value="10"/>
 
         <!--定义在从数据库获取新连接失败后重复尝试的次数。默认值: 30 ;小于等于0表示无限次 --> 
          <property name="acquireRetryAttempts" value="10"/>
 
          <!--重新尝试的时间间隔,默认为:1000毫秒 --> 
          <property name="acquireRetryDelay" value="1000" />
 
          <!--关闭连接时,是否提交未提交的事务,默认为false,即关闭连接,回滚未提交的事务 --> 
          <property name="autoCommitOnClose">false</property>
 
          <!--c3p0将使用设置的 SQL 语句来完成数据库连接是否可用的检测。默认值: null --> 
          <property name="preferredTestQuery">select 1;</property>
          <!--如果为false,则获取连接失败将会引起所有等待连接池来获取连接的线程抛出异常,但是数据源仍有效保留,并在下次调用getConnection()的时候继续尝试获取连接。如果设为true,那么在尝试获取连接失败后该数据源将申明已断开并永久关闭。默认: false --> 
          <property name="breakAfterAcquireFailure">false</property>
 
          <!--每60秒检查所有连接池中的空闲连接。默认值: 0,不检查 --> 
          <property name="idleConnectionTestPeriod">60</property>
          <!--c3p0全局的PreparedStatements缓存的大小。如果maxStatements与maxStatementsPerConnection均为0,则缓存不生效,只要有一个不为0,则语句的缓存就能生效。如果默认值: 0 --> 
          <property name="maxStatements">0</property>
          <!--maxStatementsPerConnection定义了连接池内单个连接所拥有的最大缓存statements数。默认值: 0 --> 
          <property name="maxStatementsPerConnection">0</property>
     </bean>

tomcat 作为常用的容器,也可以配置连接池,具体各个参数的作用参考官方文档,多数情况下可以参考如下配置。

PS:Durid 的连接池配置基本雷同,也可作为参考,更详细的设置参考 Durid 官方文档

<Resource name="jdbc/TestDB"
          auth="Container"
          type="javax.sql.DataSource"
          factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"
          <!-- 检查连接池中的空闲连接。默认值: false -->
          testWhileIdle="true"
          <!-- 获取连接池的连接时,检查连接是否可用。默认值: true -->
          testOnBorrow="true"
          <!-- 连接放回连接池时,检查连接是否可用。默认值: false -->
          testOnReturn="false"
          <!-- 检查连接是否可用时用的语句。默认值: NULL -->
          validationQuery="SELECT 1"
          <!-- 触发空闲连接检查的时间阈值,单位为毫秒。默认值: 30*60*1000 -->
          minEvictableIdleTimeMillis="60000"
          <!-- 连接池的最大活跃连接数,硬性限制,不会超过这个值。-->
          maxTotal="40"
          <!-- 连接池的最少保持的空闲连接数。-->
          minIdle="20"
          <!-- 连接池的最多保持的空闲连接数。-->
          maxIdle="40"
          <!-- 启动连接池时,初始化的连接数。-->
          initialSize="20"
          <!-- 无连接可用时,等待 maxWaitMillis 后再抛出异常,单位为毫秒。-->
          maxWaitMillis="3000"
          <!-- 用户名。-->
          username="root"
          <!-- 密码。-->
          password="password"
          <!-- Driver 包名。-->
          driverClassName="com.mysql.jdbc.Driver"
          <!-- 数据库连接的 url。-->
          url="jdbc:mysql://localhost:3306/mysql"/>

Python

Python 的连接池一般使用 DBUtils,详细使用方式和代码 sample 参考官方文档

根据实际使用的 python 版本下载对应的 DBUtil 包。

DBUtils提供两种外部接口:

  • PersistentDB:提供线程专用的数据库连接,并自动管理连接。
  • PooledDB:提供线程间可共享的数据库连接,并自动管理连接。

一般来说,PooledDB 的数据库连接耗时更稳定,大多数情况下都推荐使用。

挑选 PooledDB 一部分常用的参数进行说明:

  • mincached:连接池初始化的总连接数,默认值为 0,不初始化任何连接,推荐使用 10。
  • maxcached:连接池中最大的空闲连接数量,默认值为 0,无限制,推荐使用 20。
  • maxshared:最大的共享连接数,默认值为 0,所有连接都为独占,推荐使用 20。
  • maxconnections:连接池的最大连接数,硬性限制,默认值为0,无限制,推荐使用 40。
  • blocking:决定连接数达到上限时的行为。设置为 True 时会进入等待状态知道连接数降低;设置为 False 时会直接报错,默认值为 False,推荐使用默认值。
  • maxusage:单个连接被复用的次数,达到次数之后会关闭并重新打开连接,默认值为 0,无限制,推荐使用默认值。
  • setsession:连接使用前自动执行的 SQL 语句。默认值为 NULL,推荐使用默认值。
  • ping: 控制使用 ping() 方法检测连接的方式,(0 = 不检查;1 = 默认,从连接池获取的时候进行检查;2 = 创建游标时检查;4 = 执行查询时检查; 7 = 全部检查,包含 1,2,4 的所有场景),推荐使用默认值。

C & C++

C 和 C++ 可以使用 libzdb 来管理数据库连接池(线程安全),支持Mysql,Oracle,SQLite,PostgreSQL,目前仅能在 Linux 下使用。

具体原理与代码 Sample 参考官方文档

连接池的配置可以动态修改,需要调用对应的 Set 方法来设置,也可以通过对应的 Get 方法来获取当前设置。

  • ConnectionPool_getURL:获取连接池的 URL。
  • ConnectionPool_setInitialConnections:设置连接池初始化时创建的连接数。
    • 推荐设置为 10。
  • ConnectionPool_getInitialConnections:获取连接池初始化时需要创建的连接数,硬性限制
  • ConnectionPool_setMaxConnections:设置连接池的最大连接数。
    • 推荐设置为 40 或者更高。
  • ConnectionPool_getMaxConnections:获取连接池的最大连接数设置。
  • ConnectionPool_setConnectionTimeout:设置连接池空闲连接的超时时间。
    • 推荐设置为 60,单位为秒。
  • ConnectionPool_getConnectionTimeout:获取连接池空闲连接的超时时间设置。
  • ConnectionPool_setAbortHandler:设置 fatal error 出现时调用的方法。
  • ConnectionPool_setReaper:开启连接池中的 Reaper 线程,清理空闲连接。
    • 推荐开启,会定期清理超过超时时间的空闲连接。
  • ConnectionPool_size:获取连接池中当前的连接总数。
  • ConnectionPool_active:获取连接池中当前的活跃连接数。

go

go 语言自带的 database/sql 库中已经包含了连接池的实现,连接池配置的细节信息参考官方文档,此处仅列出常用参数的介绍。

go 语言有关数据库相关的设置分为两个部分,一部分是添加在 DataSourceName 中的,示例如下。

user:password@tcp(localhost:5555)/dbname?tls=skip-verify&autocommit=true

这类参数中比较常用的包括:

  • charset:设置客户端的字符集。如果连接的是 Dumbo 实例,推荐保持与数据库的设置相同。
  • collation:字符集的排序方式,推荐不进行特殊设置。
  • clientFoundRows:设置为 true 时,Update 语句会返回语句匹配的行数,而不是改变的行数,默认值为 false。
  • maxAllowedPacket:设置最大的 packet 大小,默认值为 0,使用服务器端的设置,推荐使用默认值。
  • multiStatements:设置是否支持在一次查询中执行多个语句,执行多个语句时仅返回第一个语句的结果,存在 SQL 注入的风险,默认值为 0,推荐使用默认值。
  • readTimeout:I/O 读的超时时间,单位可以用 s,m,h,默认为 0,推荐使用 5s。
  • writeTimeout:I/O 写的超时时间,单位可以用 s,m,h,默认为 0,推荐使用 5s。
  • timeout:创建数据连接的超时时间,单位可以用 s,m,h,默认为系统设置,推荐使用 5s。
  • MySQL 中的变量,例如 autocommit=true,部分 String 类的值需要使用特殊表达方式,参考官方介绍

go 语言中关于连接池的参数设置,需要使用 function 来完成。详细的列表参考官方文档

较常用的 function 包括如下:

  • SetConnMaxLifetime:设置连接可以被重用的时间阈值,超过该阈值的连接会被关闭,设置为 0 则该连接无论过了多久可以被重用。类似于最大 idle 时间的意义,推荐使用 3600。
  • SetMaxIdleConns:最大的空闲连接数,设置为 0 代表不保留空闲连接,当前默认值为 2,推荐使用 20。
  • SetMaxOpenConns:最大的连接数,硬性限制,设置为 0 代表不限制,当前默认值为 0,推荐使用 40。

其他的语言

对于任何编程语言,都推荐使用数据库连接池来访问数据库,避免一些潜在的风险。虽然无法把所有语言的数据库连接池配置都一一列出,但是配置参数方面,可以参考如下几个通用的策略:

  • 限制最大连接数:形如 maxConn 等参数,务必根据实际 Server 或者容器的总数来进行合理的设置,避免达到后端数据库的连接数上限(比如 MySQL 的 max-connection),该设置需要注意是硬上限还是软上限,一般来说连接数满了之后能设置是 blocking 或者 Exception 等应对策略的,都是硬上限。
  • 设置合理的空闲连接数:形如 maxIdleConn 的参数,在连接池中保持一定量的空闲连接能较好的应对突发性的业务高峰,同时空闲的连接通常不会对数据库造成明显的负担。
  • 开启连接检测、心跳等功能:形如 idlecheck,heartbeat 等参数,能够确保连接池中所有的连接均为可用状态,减少异常的发生,也避免大量失效的连接引起业务大量报错与重试。