目录
1.问题复现:
2.问题分析:
3.解决问题·如何禁用 Ping Method
4.禁用 Ping Method的后果
5.两者之间的权宜之计:
[ERROR] 2023-07-23 18:03:03,427 method:com.alibaba.druid.pool.DruidAbstractDataSource.testConnectionInternal(DruidAbstractDataSource.java:1481)
discard long time none received connection.
这个错误在执行sql的时候会频繁打印出来,就很烦!
首先上面的异常并不影响程序的正常运行,但作为程序员看到程序中不停的出现异常还是难以忍受的。所以还是要刨根问底的解决一下的。
根据上面的异常错误,带着大家深入源码找问题原因:
如果大家是使用的idea的话,可以双击Shift弹出搜索查找框,输入 DruidAbstractDataSource也就是上面异常的类名称,接着在这个类中搜索 testConnectionInternal 同理也是抛出这个异常的方法。到这里我们就找到这个问题的源码,下面我们开始分析这个问题的原因:
if (valid && isMySql) { // unexcepted branch
long lastPacketReceivedTimeMs = MySqlUtils.getLastPacketReceivedTimeMs(conn);
if (lastPacketReceivedTimeMs > 0) {
long mysqlIdleMillis = currentTimeMillis - lastPacketReceivedTimeMs;
if (lastPacketReceivedTimeMs > 0 //
&& mysqlIdleMillis >= timeBetweenEvictionRunsMillis) {
discardConnection(holder);
String errorMsg = "discard long time none received connection. "
+ ", jdbcUrl : " + jdbcUrl
+ ", jdbcUrl : " + jdbcUrl
+ ", lastPacketReceivedIdleMillis : " + mysqlIdleMillis;
LOG.error(errorMsg);
return false;
}
}
}
上述代码中,MySqlUtils.getLastPacketReceivedTimeMs(conn) 是获取上一次使用的时间,mysqlIdleMillis 就是计算出来空闲的时间,timeBetweenEvictionRunsMillis 是常量60秒。如果连接空闲了60秒以上,那就discardConnection(holder) 丢弃这个旧连接并顺带打印了一个日志LOG.warn(errorMsg)。
在上述代码中,我们看到进入该业务逻辑是有前提条件的,也就是valid和isMySql变量同时为true。isMySql为true是必须的,我们使用的本身就是Mysql数据库。那么是否可以让valid为false呢?这样不就不会进入该业务处理了吗?
来看看valid的来源,还是在该方法的上面一点:
boolean valid = validConnectionChecker.isValidConnection(conn, validationQuery, validationQueryTimeout);
我们找到validConnectionChecker的Mysql实现子类MySqlValidConnectionChecker,该类中对isValidConnection的实现如下:
public boolean isValidConnection(Connection conn, String validateQuery, int validationQueryTimeout) throws Exception {
if (conn.isClosed()) {
return false;
}
if (usePingMethod) {
if (conn instanceof DruidPooledConnection) {
conn = ((DruidPooledConnection) conn).getConnection();
}
if (conn instanceof ConnectionProxy) {
conn = ((ConnectionProxy) conn).getRawObject();
}
if (clazz.isAssignableFrom(conn.getClass())) {
if (validationQueryTimeout <= 0) {
validationQueryTimeout = DEFAULT_VALIDATION_QUERY_TIMEOUT;
}
try {
ping.invoke(conn, true, validationQueryTimeout * 1000);
} catch (InvocationTargetException e) {
Throwable cause = e.getCause();
if (cause instanceof SQLException) {
throw (SQLException) cause;
}
throw e;
}
return true;
}
}
String query = validateQuery;
if (validateQuery == null || validateQuery.isEmpty()) {
query = DEFAULT_VALIDATION_QUERY;
}
Statement stmt = null;
ResultSet rs = null;
try {
stmt = conn.createStatement();
if (validationQueryTimeout > 0) {
stmt.setQueryTimeout(validationQueryTimeout);
}
rs = stmt.executeQuery(query);
return true;
} finally {
JdbcUtils.close(rs);
JdbcUtils.close(stmt);
}
}
我们可以看到上述方法中有三个返回的地方:第一个连接已关闭;第二个使用ping的形式进行检查;第三,使用select 1的方式进行检查。而使用ping的形式检查时,无论是否抛异常都会返回true。这里我们禁用该模式即可。
进入ping的业务逻辑主要靠变量usePingMethod来判断,追踪代码会发现在这里进行的设置:
@Override
public void configFromProperties(Properties properties) {
String property = properties.getProperty("druid.mysql.usePingMethod");
if ("true".equals(property)) {
setUsePingMethod(true);
} else if ("false".equals(property)) {
setUsePingMethod(false);
}
}
那么,也就是说,当我们把系统属性druid.mysql.usePingMethod设置为false即可禁用该功能。
找到了问题的根源,那么剩下的就是如何禁用了,通常有三种形式。
第一,在启动程序时在运行参数中增加:-Ddruid.mysql.usePingMethod=false。
第二,在Spring Boot项目中,可在启动类中添加如下静态代码快:
static {
System.setProperty("druid.mysql.usePingMethod","false");
}
第三,类文件配置。在项目的DruidConfig类中新增加:
/*
* 解决druid 日志报错:discard long time none received connection:xxx
* */
@PostConstruct
public void setProperties(){
System.setProperty("druid.mysql.usePingMethod","false");
}
至此,已可以成功关闭该功能,异常信息再也不会出现了。
当设置为 false
时,Druid连接池将不会使用 "ping" 方法来检测连接的有效性。通常,ping是一种简单的方式,向数据库发送一个小的查询语句,来确认连接是否仍然有效。如果连接无效,将抛出异常或返回错误状态,连接池可以据此来废弃或重新创建该连接。
设置 druid.mysql.usePingMethod
为 false
可能会导致以下后果:
不会自动检测连接的有效性:连接池将不会主动检测连接的有效性。这意味着即使数据库连接已经失效,连接池也不会立即知道,而继续使用失效的连接可能会导致异常或错误的数据库操作。
影响连接的可靠性:不进行连接有效性检测可能会导致连接池中累积无效连接,这些连接可能无法正常工作,从而影响应用程序的可靠性。
潜在的数据库异常:由于连接池不会自动检测连接的有效性,应用程序可能会在使用已失效连接时遇到数据库异常,例如连接超时或无效查询。
在设置 usePingMethod
之前,请确保了解应用程序的实际需求和数据库连接的特性。如果你选择将其设置为 false
,建议实施其他有效的连接有效性检测方法,例如设置合适的连接超时时间或使用合理的数据库查询来确认连接是否有效。总之,谨慎设置此属性,并确保在生产环境中进行全面的测试和性能评估。
如果你希望继续检测连接的有效性,但又不想关闭 druid.mysql.usePingMethod
配置,你可以考虑以下方法:
使用连接池默认设置:如果你没有手动设置 druid.mysql.usePingMethod
配置,那么连接池会使用默认的设置。在大多数情况下,默认设置是启用连接有效性检测的,即使用 "ping" 方法来检测连接是否有效。
设置其他连接有效性检测配置:Druid连接池提供了其他连接有效性检测的配置选项,例如 validationQuery
和 validationQueryTimeout
。你可以使用这些配置来自定义连接有效性检测的方法。通过配置一个有效的 SQL 查询语句(validationQuery
)并设置合适的超时时间(validationQueryTimeout
),连接池将使用该查询来检测连接的有效性。
// 设置连接有效性检测的SQL查询语句和超时时间
druidDataSource.setValidationQuery("SELECT 1");
druidDataSource.setValidationQueryTimeout(5); // 设置超时时间为5秒
这样,当连接从连接池中取出或归还时,连接池将执行 SELECT 1
查询来确认连接是否有效。
使用定期检测:如果你想要自定义更复杂的连接有效性检测逻辑,可以考虑实现一个定期的连接检测任务。你可以创建一个后台线程,定期检测连接的有效性,并在连接无效时将其废弃。这种方法可以更灵活地控制连接有效性检测的逻辑,但也需要更多的代码实现。
不管你选择哪种方法,都需要确保在应用程序中实现合适的连接有效性检测,以确保连接池中的连接始终保持可靠和有效。连接有效性检测是确保数据库连接池正常运行的关键部分,应当根据实际情况进行适当的配置和优化。