Druid配置参数详解-testOnBorrow
Druid是一个由阿里开源的数据库连接池,Druid的配置非常丰富,但是设置不当会对生产环境造成严重影响,网上Druid的资料虽多,但大部分都是互相复制粘贴,有很多不准确甚至完全错误的描述,Druid已经开源很久,而且作者WenShao的工作重心也已经不在Druid上,有些功能估计他自己都不太了解了。本系列将从源代码的角度分析Druid目前的最新版本(1.1.21)各个常用的配置项的具体含义以及是怎么起作用的。
画外音:目前Druid在开源中国举办的2019年度最受欢迎中国开源软件中排名第7名,支持Druid的朋友可以去投票哇。2019年度最受欢迎中国开源软件
testOnBorrow是什么意思?
testOnBorrow:如果为true(默认false),当应用向连接池申请连接时,连接池会判断这条连接是否是可用的。
testOnBorrow什么时候会用到?
这个参数主要在DruidDataSource的getConnectionDirect方法中用到
public DruidPooledConnection getConnectionDirect(long maxWaitMillis) throws SQLException {
int notFullTimeoutRetryCnt = 0;
for (;;) {
// handle notFullTimeoutRetry
DruidPooledConnection poolableConnection;
try {
poolableConnection = getConnectionInternal(maxWaitMillis);
} catch (GetConnectionTimeoutException ex) {
if (notFullTimeoutRetryCnt <= this.notFullTimeoutRetryCount && !isFull()) {
notFullTimeoutRetryCnt++;
if (LOG.isWarnEnabled()) {
LOG.warn("get connection timeout retry : " + notFullTimeoutRetryCnt);
}
continue;
}
throw ex;
}
//测试即将返回的连接是否可用
if (testOnBorrow) {
boolean validate = testConnectionInternal(poolableConnection.holder, poolableConnection.conn);
if (!validate) {
if (LOG.isDebugEnabled()) {
LOG.debug("skip not validate connection.");
}
discardConnection(poolableConnection.holder);
continue;
}
}
//。。。
}
连接池是如何判断连接是否有效的?
- 如果是常用的数据库,则使用${DBNAME}ValidConnectionChecker进行判断,比如Mysql数据库,使用MySqlValidConnectionChecker的isValidConnection进行判断;
- 如果是其他数据库,则使用validationQuery判断;
具体验证规则可查看:Druid配置参数详解-validationQuery
protected boolean testConnectionInternal(DruidConnectionHolder holder, Connection conn) {
String sqlFile = JdbcSqlStat.getContextSqlFile();
String sqlName = JdbcSqlStat.getContextSqlName();
if (sqlFile != null) {
JdbcSqlStat.setContextSqlFile(null);
}
if (sqlName != null) {
JdbcSqlStat.setContextSqlName(null);
}
try {//如果是常用的数据库,则使用相关数据库的validConnectionChecker进行验证,比如MysqlValidConnectionChecker,否则使用validationQuery验证
if (validConnectionChecker != null) {
boolean valid = validConnectionChecker.isValidConnection(conn, validationQuery, validationQueryTimeout);
long currentTimeMillis = System.currentTimeMillis();
if (holder != null) {
holder.lastValidTimeMillis = currentTimeMillis;
holder.lastExecTimeMillis = currentTimeMillis;
}
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;
}
}
}
if (valid && onFatalError) {
lock.lock();
try {
if (onFatalError) {
onFatalError = false;
}
} finally {
lock.unlock();
}
}
return valid;
}
if (conn.isClosed()) {
return false;
}
if (null == validationQuery) {
return true;
}
Statement stmt = null;
ResultSet rset = null;
try {
stmt = conn.createStatement();
if (getValidationQueryTimeout() > 0) {
stmt.setQueryTimeout(validationQueryTimeout);
}
rset = stmt.executeQuery(validationQuery);
if (!rset.next()) {
return false;
}
} finally {
JdbcUtils.close(rset);
JdbcUtils.close(stmt);
}
if (onFatalError) {
lock.lock();
try {
if (onFatalError) {
onFatalError = false;
}
} finally {
lock.unlock();
}
}
return true;
} catch (Throwable ex) {
// skip
return false;
} finally {
if (sqlFile != null) {
JdbcSqlStat.setContextSqlFile(sqlFile);
}
if (sqlName != null) {
JdbcSqlStat.setContextSqlName(sqlName);
}
}
}
public boolean isValidConnection(Connection conn, String validateQuery, int validationQueryTimeout) throws Exception {
if (conn.isClosed()) {
return false;
}
//如果数据库驱动有pingInternal方法,则使用pingInternal方法判断,否则使用validateQuery进行判断
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);
}
}
如果验证不通过怎么办?
验证不通过则会直接关闭该连接,并重新从连接池获取下一条连接;
//测试即将返回的连接是否可用
if (testOnBorrow) {
boolean validate = testConnectionInternal(poolableConnection.holder, poolableConnection.conn);
if (!validate) {
if (LOG.isDebugEnabled()) {
LOG.debug("skip not validate connection.");
}
//验证不通过则直接剔除该连接,并重新获取下一条连接
discardConnection(poolableConnection.holder);
continue;
}
}
总结
testOnBorrow能够确保我们每次都能获取到可用的连接,但如果设置成true,则每次获取连接的时候都要到数据库验证连接有效性,这在高并发的时候会造成性能下降,可以将testOnBorrow设成false,testWhileIdle设置成true这样能获得比较好的性能。