记一次连接池配置导致的问题

本文记录了一次由于连接池配置不当导致的错误,问题在于数据库服务端线程池关闭了长时间未使用的连接,而客户端未能及时察觉。通过理解长连接的工作原理,特别是服务端的超时机制,发现当前的连接池配置缺乏检测功能。参考相关资料后,增加了检测机制,成功解决了问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近测试环境老抛出这个错,

Caused by: com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: The last packet successfully received from the server was 53,014,427 milliseconds ago.  The last packet sent successfully to the server was 53,014,427 milliseconds ago. is longer than the server configured value of 'wait_timeout'. You should consider either expiring and/or testing connection validity before use in your application, increasing the server configured values for client timeouts, or using the Connector/J connection property 'autoReconnect=true' to avoid this problem.
	at sun.reflect.GeneratedConstructorAccessor58.newInstance(Unknown Source)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
	at com.mysql.jdbc.Util.handleNewInstance(Util.java:425)
	at com.mysql.jdbc.SQLError.createCommunicationsException(SQLError.java:990)
	at com.mysql.jdbc.MysqlIO.send(MysqlIO.java:3706)
	at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2506)
	at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2675)
	at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2465)
	at com.mysql.jdbc.ConnectionImpl.setAutoCommit(ConnectionImpl.java:4776)
	at org.apache.commons.dbcp.DelegatingConnection.setAutoCommit(DelegatingConnection.java:331)
	at org.apache.commons.dbcp.PoolingDataSource$PoolGuardConnectionWrapper.setAutoCommit(PoolingDataSource.java:317)
	at org.springframework.jdbc.datasource.DataSourceTransactionManager.doBegin(DataSourceTransactionManager.java:267)
	... 58 common frames omitted
	
引起原因:com.mysql.jdbc.exceptions.jdbc4.CommunicationsException:从服务器成功接收到的最后一个数据包是53,014,427毫秒之前。 成功发送到服务器的最后一个数据包是53,014,427毫秒之前。 大于服务器配置的“ wait_timeout”值。 您应考虑在应用程序中使用连接之前使连接有效性到期和/或对其进行测试,或者增加服务器为客户端超时配置的值,或者使用Connector / J连接属性'autoReconnect = true'来避免此问题。


Caused by: java.net.SocketException: 断开的管道 (Write failed)
	at java.net.SocketOutputStream.socketWrite0(Native Method)
	at java.net.SocketOutputStream.socketWrite(SocketOutputStream.java:111)
	at java.net.SocketOutputStream.write(SocketOutputStream.java:155)
	at java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:82)
	at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:140)
	at com.mysql.jdbc.MysqlIO.send(MysqlIO.java:3688)
	... 65 common frames omitted

分析可知,数据库服务端也是一个线程池,服务端的线程长时间不用、关闭了,可客户端并不知情,针对这种情况,可想而知、需要一个检测机制。

数据库是长连接的,我们认识一下长连接:

而长连接通常就是:

连接-》数据传输-》保持连接-》数据传输-》保持连接-》…………-》关闭连接;

这就要求长连接在没有数据通信时,定时发送数据包,以维持连接状态,短连接在没有数据传输时直接关闭就行了

什么时候用长连接,短连接?

长连接主要用于在少数客户端与服务端的频繁通信,因为这时候如果用短连接频繁通信常会发生Socket出错,并且频繁创建Socket连接也是对资源的浪费。

但是对于服务端来说,长连接也会耗费一定的资源,需要专门的线程(unix下可以用进程管理)来负责维护连接状态。
总之,长连接和短连接的选择要视情况而定。

 

首先,如果使用了长连接而长期没有对数据库进行任何操作,那么在timeout值后,mysql server就会关闭此连接,而客户端在执行查询的时候就会得到一个类似于“MySQL server has gone away“这样的错误。

在使用mysql_real_connect连接数据库之后,再使用mysql_options( &mysql, MYSQL_OPT_RECONNECT, … ) 来设置为自动重连。这样当mysql连接丢失的时候,使用mysql_ping能够自动重连数据库。如果是在mysql 5.1.6之前,那么则应在每次执行完real_connect 之后执行mysql_options( &mysql, MYSQL_OPT_RECONNECT, … ) ,如果是mysql 5.1.6+,则在connect之前执行一次就够了。

通过对长连接的了解,服务端针对不用的连接可以设置–timeout,废弃连接、客户端需要知道这个操作。

追踪数据库连接池配置,发现只有线程池基础配置,没有检测功能

<bean id="mysqlDataSource" class="org.apache.commons.dbcp.BasicDataSource"> 
		<property name="driverClassName" value="${db.mysql.driverClassName}"/> 
		<property name="url" value="${db.mysql.url}&amp;characterEncoding=UTF-8&amp;allowMultiQueries=true"/>
		<property name="username" value="${db.mysql.username}" /> 
		<property name="password" value="${db.mysql.password}" /> 
		// 指明连接是否被空闲连接回收器(如果有)进行检验.如果检测失败,则连接将被从池中去除.
		// 验证使用的SQL语句  
		<property name="validationQuery" value="select 1" />
        <!-- 初始化连接大小 -->
        <property name="initialSize" value="10"></property>
        <!-- 连接池最大数量 -->
        <property name="maxActive" value="100"></property>
        <!-- 连接池最大空闲 -->
        <property name="maxIdle" value="50"></property>
        <!-- 连接池最小空闲 -->
        <property name="minIdle" value="10"></property>
        <!-- 获取连接最大等待时间 -->
        <property name="maxWait" value="60000"></property>
	</bean>

参考 https://www.cnblogs.com/chrischennx/p/7279215.html?utm_source=itdadao&utm_medium=referral
添加检测功能

修改后配置为,

<bean id="mysqlDataSource" class="org.apache.commons.dbcp.BasicDataSource"> 
		<property name="driverClassName" value="${db.mysql.driverClassName}"/> 
		<property name="url" value="${db.mysql.url}&amp;characterEncoding=UTF-8&amp;allowMultiQueries=true"/>
		<property name="username" value="${db.mysql.username}" /> 
		<property name="password" value="${db.mysql.password}" /> 
		// 指明连接是否被空闲连接回收器(如果有)进行检验.如果检测失败,则连接将被从池中去除. 
		<property name="testWhileIdle" value="true" /> 
		// 借出连接时不要测试,否则很影响性能  
		<property name="testOnBorrow" value="true" />  
		<property name="testOnReturn" value="true" />  
		// 验证使用的SQL语句  
		<property name="validationQuery" value="select 1" />
        <!-- 初始化连接大小 -->
        <property name="initialSize" value="10"></property>
        <!-- 连接池最大数量 -->
        <property name="maxActive" value="100"></property>
        <!-- 连接池最大空闲 -->
        <property name="maxIdle" value="50"></property>
        <!-- 连接池最小空闲 -->
        <property name="minIdle" value="10"></property>
        <!-- 获取连接最大等待时间 -->
        <property name="maxWait" value="60000"></property>
		// 每30秒运行一次空闲连接回收器 
		<property name="timeBetweenEvictionRunsMillis" value="60000" />
		// 在每次空闲连接回收器线程(如果有)运行时检查的连接数量 
		<property name="numTestsPerEvictionRun" value="10" />
		// 池中的连接空闲30分钟后被回收 
		<property name="minEvictableIdleTimeMillis" value="120000" />  
	</bean>

问题解决。。

### Druid 连接池 Properties 文件配置详解 #### 数据源基本属性配置 在 `druid.properties` 文件中,可以通过设置数据源的基本属性来初始化连接池。这些属性包括但不限于: - **url**: 数据库的 JDBC URL 地址。 - **username**: 访问数据库所使用的用户名。 - **password**: 用户名对应的密码。 为了提高安全性并遵循最佳实践,建议通过 ConfigFilter 来管理敏感信息如密码[^1]。 ```properties # 基本属性配置示例 spring.datasource.druid.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC spring.datasource.druid.username=root spring.datasource.druid.password= ``` #### 初始化大小与最大活跃数设定 合理调整初始容量以及最大活动连接数量对于优化应用性能至关重要。以下是两个重要的参数: - **initialSize**: 初始创建的连接数目,默认值通常为 0 或者较小数值;可以根据实际需求适当增加此值以减少首次请求时建立新连接的时间开销。 - **maxActive**: 定义了允许的最大并发连接总数,在高负载情况下应谨慎评估服务器资源状况后再做决定。 ```properties # 设置初始化连接数和最大活跃连接数 spring.datasource.druid.initial-size=5 spring.datasource.druid.max-active=20 ``` #### 超时时间控制 为了避免长时间占用连接而导致其他操作无法正常获取可用链接的情况发生,需指定合理的等待超时时间和查询超时时间: - **maxWait**: 当没有可用连接时,调用者的最长等待毫秒数,超过该时限则抛出异常。 - **timeBetweenEvictionRunsMillis**: 表明驱逐线程运行间隔多久扫描一次空闲对象列表,单位为毫秒。 - **minEvictableIdleTimeMillis**: 对象至少保持多长时间未被访问即被认为是闲置状态,从而可能被回收利用。 ```properties # 配置超时时间 spring.datasource.druid.max-wait=60000 spring.datasource.druid.time-between-eviction-runs-millis=60000 spring.datasource.druid.min-evictable-idle-time-millis=300000 ``` #### 测试策略定义 确保每次从池子里取出的对象都是有效的非常重要,因此需要配置测试语句及相关选项: - **validationQuery**: SQL 查询命令用于验证连接的有效性,一般选择简单快速返回结果集的小型查询即可满足目的。 - **testWhileIdle** 和 **testOnBorrow**: 控制何时执行有效性检测逻辑——是在借入之前还是处于空闲状态下定期抽查。 ```properties # 测试策略配置 spring.datasource.druid.validation-query=SELECT 'x' spring.datasource.druid.test-on-borrow=false spring.datasource.druid.test-while-idle=true ``` #### 日志录与统计分析支持 Druid 提供强大的内置监控特性,能够帮助开发者更好地理解应用程序内部的工作机制及其对外部依赖的表现情况: - **logSlowSql**: 是否开启慢SQL日志打印功能; - **slowSqlMillis**: 设定判定一条SQL是否属于“慢”的阈值标准(毫秒),低于这个值不会触发警告通知; - **filters**: 启动特定过滤器链表,比如 stat 可用来收集统计数据,wall 有助于防止潜在的安全风险。 ```properties # 开启日志录与统计分析 spring.datasource.druid.log-slow-sql=true spring.datasource.druid.slow-sql-millis=3000 spring.datasource.druid.filters=stat, wall ``` 以上就是关于 Druid 连接池 properties 文件配置的一些详细介绍。当然还有更多高级特性和个性化定制可供探索学习。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值