简介
说明
本文介绍Java中操作MySQL时一个报错的原因及解决方案。这个报错是:com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure
完整报错信息
2022-02-16 08:03:00 [WARN] [com.zaxxer.hikari.pool.ProxyConnection:160] - HikariPool-1 - Connection com.mysql.jdbc.JDBC4Connection@1f2bca8b marked as broken because of SQLSTATE(08S01), ErrorCode(0) Caused by: com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure The last packet successfully received from the server was 60,061 milliseconds ago. The last packet sent successfully to the server was 60,060 milliseconds ago. at sun.reflect.GeneratedConstructorAccessor305.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:989) at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:3559) at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:3459) at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3900) at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2527) at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2680) at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2487) at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:1858) at com.mysql.jdbc.PreparedStatement.executeUpdateInternal(PreparedStatement.java:2079) at com.mysql.jdbc.PreparedStatement.executeUpdateInternal(PreparedStatement.java:2013) at com.mysql.jdbc.PreparedStatement.executeLargeUpdate(PreparedStatement.java:5104) at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:1998) at com.zaxxer.hikari.pool.ProxyPreparedStatement.executeUpdate(ProxyPreparedStatement.java:61) at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.executeUpdate(HikariProxyPreparedStatement.java) at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:204) ... 74 common frames omitted
原因分析
导致这个报错的原因有两个
- 数据库重启
- 客户端长时间没操作数据库(数据库空闲连接的时间超过设置的最大timemout时间)
- 导致数据库会强行断开已有的链接,就会报这个异常。
对于第二个原因(客户端长时间没操作数据库)
MySQL服务器默认的“wait_timeout”是28800秒,即:8小时。这意味着如果一个连接的空闲时间超过8个小时,MySQL将自动断开该连接,而客户端连接池却认为该连接还是有效的(因为并未校验连接的有效性),当客户端申请使用该连接时,就会导致上面的报错。
查看MySQL的wait_timeout的方法:
mysql > show global variables like 'wait_timeout'; +---------------+-------+ | Variable_name | Value | +---------------+-------+ | wait_timeout | 28800 | +---------------+-------+ 1 row in set (0.00 sec)
解决方案
1. 重启应用(临时方案)
重启应用服务器。
重启应用服务器,数据库连接池就会重新初始化, 重新获取和数据库的有效连接)
2. 客户端连接池配置探活(推荐)
在配置数据库连接池的时候需要做一些检查连接有效性的配置。
例1:Druid配置
这里以Druid为例,相关配置如下(详细配置(github))
字段名 | 默认值 | 说明 |
validationQuery | 用来检测连接是否有效的sql,要求是一个查询语句,常用select ‘x’。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。 | |
validationQueryTimeout | 单位:秒,检测连接是否有效的超时时间。底层调用jdbc Statement对象的void setQueryTimeout(int seconds)方法 | |
testOnBorrow | true | 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 |
testOnReturn | false | 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 |
testWhileIdle | false | 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 |
timeBetweenEvictionRunsMillis | 1分钟(1.0.14) | 有两个含义: 1) Destroy线程会检测连接的间隔时间,如果连接空闲时间大于等于minEvictableIdleTimeMillis则关闭物理连接。 2) testWhileIdle的判断依据,详细看testWhileIdle属性的说明 |
本处这样配置即可:
validationQuery: SELECT 1 testWhileIdle: true timeBetweenEvictionRunsMillis: 28000
timeBetweenEvictionRunsMillis需要小于MySQL服务端的wait_timeout。
2. hikari
另见:详细配置(github)
spring: datasource: url: jdbc:mysql://xx.xx.xx.xx:3306/xx?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=GMT%2B8 username: xxxx password: xxxxxx driver-class-name: com.mysql.jdbc.Driver type: com.zaxxer.hikari.HikariDataSource initialization-mode: always continue-on-error: true hikari: minimum-idle: 5 connection-test-query: SELECT 1 FROM DUAL maximum-pool-size: 20 auto-commit: true idle-timeout: 30000 pool-name: SpringBootDemoHikariCP max-lifetime: 60000 connection-timeout: 30000
spring.datasource.hikari.max-lifetime 的值要小于 MySQL 的 wait_timeout
3. 修改MySQL配置(不推荐)
增大数据库默认的超时等待时间(wait_timeout) 。
不建议这么改,因为如果太大,可能导致连接数较多,引起性能下降。
查看MySQL5的手册,发现Windows和Linux下wait_timeout的最大值分别是24天和365天。
修改MySQL的参数,wait_timeout最大为31536000即1年,在my.cnf中加入:
[mysqld] wait_timeout=31536000 interactive_timeout=31536000
重启生效,需要同时修改这两个参数。
4. 配置连接URL重连机制(不推荐)
在连接URL上添加参数:
&autoReconnect=true&failOverReadOnly=false
不推荐的原因如下(官网链接):
The use of this feature is not recommended, because it has side effects related to session state and data consistency when applications don’t handle SQLExceptions properly, and is only designed to be used when you are unable to configure your application to handle SQLExceptions resulting from dead and stale connections properly.
即:
不推荐开启这个特性,因为有副作用:没有正确处理SQLException时容易造成会话状态和数据一致性的问题(也就是事务)。
5. 减小连接池内连接生存周期
使之小于所设置的wait_timeout 的值
配置项:maxIdleTime
请先
!