数据库连接池中连接有效性探测的几种方式
创始人
2025-05-30 22:05:12
0

但凡涉及到数据库的应用,基本上都离不开数据库连接池工具的使用,但在使用时,对于应用而言,无需关注你获取到的连接是否可用,不像之前,有个著名的MySQL连接8小时问题,现在根本不用关心从连接池拿到的连接是否可用,你拿到了说明就是可用的,这一点是数据库连接池的框架保证的,常见的数据库连接池都有保证,今天主要探讨下SpringBoot默认的数据库连接池HikariCP和Durid连接池在连接的有效性探测的差异。

配置

从配置上来说,两者都提供了通过sql来检查活性。

Druid的不分配置如下:

validationQuery=select 'x'
testWhileIdle=true
testOnBorrow=true
testOnReturn=true

HikariCP的部分配置如下:

connectionTestQuery=select 'x'

从以上部分配置看出,两个都提供了相关配置,以保证获取的连接是有效的。

有效性检查

HikariCP的有效性检查(3.2.0)

我们先来看HikariCP的在获取连接时的有效性检查,HikariCP获取连接时的有效性检查时必须的,没有提供不需要检查的相关配置。HikariCP的连接获取是从HikariPool中获取的,部分源码实现如下:

public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBagStateListener {.../*** Get a connection from the pool, or timeout after connectionTimeout milliseconds.** @return a java.sql.Connection instance* @throws SQLException thrown if a timeout occurs trying to obtain a connection*/public Connection getConnection() throws SQLException{return getConnection(connectionTimeout);}/*** Get a connection from the pool, or timeout after the specified number of milliseconds.** @param hardTimeout the maximum time to wait for a connection from the pool* @return a java.sql.Connection instance* @throws SQLException thrown if a timeout occurs trying to obtain a connection*/public Connection getConnection(final long hardTimeout) throws SQLException{suspendResumeLock.acquire();final long startTime = currentTime();try {long timeout = hardTimeout;do {PoolEntry poolEntry = connectionBag.borrow(timeout, MILLISECONDS);if (poolEntry == null) {break; // We timed out... break and throw exception}final long now = currentTime();// 通过isConnectionAlive判断连接的活性,继承自父类PoolBaseif (poolEntry.isMarkedEvicted() || (elapsedMillis(poolEntry.lastAccessed, now) > aliveBypassWindowMs && !isConnectionAlive(poolEntry.connection))) {closeConnection(poolEntry, poolEntry.isMarkedEvicted() ? EVICTED_CONNECTION_MESSAGE : DEAD_CONNECTION_MESSAGE);timeout = hardTimeout - elapsedMillis(startTime);}else {metricsTracker.recordBorrowStats(poolEntry, startTime);return poolEntry.createProxyConnection(leakTaskFactory.schedule(poolEntry), now);}} while (timeout > 0L);metricsTracker.recordBorrowTimeoutStats(startTime);throw createTimeoutException(startTime);}catch (InterruptedException e) {Thread.currentThread().interrupt();throw new SQLException(poolName + " - Interrupted during connection acquisition", e);}finally {suspendResumeLock.release();}}...
}  abstract class PoolBase {PoolBase(final HikariConfig config){this.config = config;this.networkTimeout = UNINITIALIZED;this.catalog = config.getCatalog();this.schema = config.getSchema();this.isReadOnly = config.isReadOnly();this.isAutoCommit = config.isAutoCommit();this.exceptionOverride = UtilityElf.createInstance(config.getExceptionOverrideClassName(), SQLExceptionOverride.class);this.transactionIsolation = UtilityElf.getTransactionIsolation(config.getTransactionIsolation());this.isQueryTimeoutSupported = UNINITIALIZED;this.isNetworkTimeoutSupported = UNINITIALIZED;// 这里判断使用那种方式进行连接的探活this.isUseJdbc4Validation = config.getConnectionTestQuery() == null;this.isIsolateInternalQueries = config.isIsolateInternalQueries();this.poolName = config.getPoolName();this.connectionTimeout = config.getConnectionTimeout();this.validationTimeout = config.getValidationTimeout();this.lastConnectionFailure = new AtomicReference<>();initializeDataSource();}boolean isConnectionAlive(final Connection connection){try {try {setNetworkTimeout(connection, validationTimeout);final int validationSeconds = (int) Math.max(1000L, validationTimeout) / 1000;// 判断是否使用Jdbc4的方式进行探测if (isUseJdbc4Validation) {return connection.isValid(validationSeconds);}// 使用探测语句的方式探活try (Statement statement = connection.createStatement()) {if (isNetworkTimeoutSupported != TRUE) {setQueryTimeout(statement, validationSeconds);}statement.execute(config.getConnectionTestQuery());}}finally {setNetworkTimeout(connection, networkTimeout);if (isIsolateInternalQueries && !isAutoCommit) {connection.rollback();}}return true;}catch (Exception e) {lastConnectionFailure.set(e);logger.warn("{} - Failed to validate connection {} ({}). Possibly consider using a shorter maxLifetime value.",poolName, connection, e.getMessage());return false;}}
}

通过以上部分源码我们基本上可以知道,HikariCP有两种方式判断连接的有效性,一种是配置探测SQL的方式,另一种是UseJdbc4Validation的方式。在PoolBase的初始化片段中,是否使用Jdbc4的探活和应用是否配置了探测SQL有关:this.isUseJdbc4Validation = config.getConnectionTestQuery() == null;,如果没有配置探活SQL,那么就使用Jdbc4的探活方式,所以,HikariCP默认使用的探活方式就是Jdbc4的方式。

Druid的有效性检查(1.2.6)

通过配置部分可知,Druid的配置参数相对多一些,其中探活只能配置sql,另外两个参数testWhileIdle和testOnBorrow两个参数分别控制在连接空闲时是否探测有效性以及从连接池中获取连接池是否需要有效性探测,部分源码如下:

public class DruidDataSource extends DruidAbstractDataSource implements DruidDataSourceMBean, ManagedDataSource, Referenceable, Closeable, Cloneable, ConnectionPoolDataSource, MBeanRegistration {public DruidPooledConnection getConnectionDirect(long maxWaitMillis) throws SQLException {int notFullTimeoutRetryCnt = 0;for (;;) {// handle notFullTimeoutRetryDruidPooledConnection 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;}} else {if (poolableConnection.conn.isClosed()) {discardConnection(poolableConnection.holder); // 传入null,避免重复关闭continue;}if (testWhileIdle) {final DruidConnectionHolder holder = poolableConnection.holder;long currentTimeMillis             = System.currentTimeMillis();long lastActiveTimeMillis          = holder.lastActiveTimeMillis;long lastExecTimeMillis            = holder.lastExecTimeMillis;long lastKeepTimeMillis            = holder.lastKeepTimeMillis;if (checkExecuteTime&& lastExecTimeMillis != lastActiveTimeMillis) {lastActiveTimeMillis = lastExecTimeMillis;}if (lastKeepTimeMillis > lastActiveTimeMillis) {lastActiveTimeMillis = lastKeepTimeMillis;}long idleMillis                    = currentTimeMillis - lastActiveTimeMillis;long timeBetweenEvictionRunsMillis = this.timeBetweenEvictionRunsMillis;if (timeBetweenEvictionRunsMillis <= 0) {timeBetweenEvictionRunsMillis = DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS;}if (idleMillis >= timeBetweenEvictionRunsMillis|| idleMillis < 0 // unexcepted branch) {boolean validate = testConnectionInternal(poolableConnection.holder, poolableConnection.conn);if (!validate) {if (LOG.isDebugEnabled()) {LOG.debug("skip not validate connection.");}discardConnection(poolableConnection.holder);continue;}}}}if (removeAbandoned) {StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();poolableConnection.connectStackTrace = stackTrace;poolableConnection.setConnectedTimeNano();poolableConnection.traceEnable = true;activeConnectionLock.lock();try {activeConnections.put(poolableConnection, PRESENT);} finally {activeConnectionLock.unlock();}}if (!this.defaultAutoCommit) {poolableConnection.setAutoCommit(false);}return poolableConnection;}}
}

从以上源码可知,Druid从连接池获取连接时先通过参数testOnBorrow判断是否需要检测,其内部的检测是通过配置的validationQuery进行查询检查的,并没有发现Jdbc4的方式进行有效性探测。

HikariCP默认的有效性检查(Jdbc4)有什么优点

JDBC4的isValid方法来测试连接是否可用,是通过向数据库服务器发送一个ping请求来实现的。这个ping请求的实现方式可能因数据库厂商而异,但通常包括向数据库服务器发送一个简单的网络数据包,以测试连接是否正常。

相比之下,使用isValid方法测试连接是否可用的优点是:

  1. 更快速和更可靠:isValid方法不需要对数据库执行任何实际的操作,而只需要测试连接是否正常。因此,它比传统方法更快速和更可靠。
  2. 更简单:isValid方法是JDBC4的标准API之一,因此它更容易使用和理解。而传统方法需要编写更多的代码,并且需要更多的知识和经验。
  3. 更兼容和可移植:isValid方法是一个标准的JDBC4 API,因此它可以在各种操作系统和网络环境中使用,从而提高了连接测试的兼容性和可移植性。

JDBC4的isValid方法的原理是基于底层网络连接的有效性进行检测,它使用了底层协议的心跳机制来检测连接的有效性。当调用isValid方法时,JDBC驱动程序会发送一个心跳包到数据库服务器,等待数据库服务器的响应。如果在指定的超时时间内收到了响应,则认为连接是有效的,否则认为连接已经失效。

总之,使用isValid方法测试连接是否可用,比传统方法更快速、更简单、更可靠、更兼容和更可移植。因此,建议使用isValid方法来测试连接是否可用。

相关内容

热门资讯

最新或2023(历届)秋天的怀... 第1篇:秋天的怀念读后感300字  读了《秋天的怀念》后,我深深地体会到了妈妈的爱比天空的太阳还要灿...
秋天的怀念读后感400字 秋天...  【1】秋天的怀念读后感400字  读了《秋天的怀念》后,我深深地体会到了妈妈的爱比天空的太阳还要灿...
最新或2023(历届)秋天的怀...   秋天的怀念读后感600字第一篇:  这是一个感人肺腑的故事,文中写了一个重病缠身的母亲,为了儿子...
秋天的怀念读后感500字 秋天...   秋天的怀念读后感500字【1】  我曾读过一篇文章——《秋天的怀念》,这篇文章使我体会到了浓浓的...
最新或2023(历届)秋天的怀...   秋天的怀念读后感作文600字篇一:  作为小树应该感谢土地,因为土地养育了它;作为小鱼应该感谢大...
JAVASE(3.18) 目录 ​编辑 1.抽象类和抽象方法 2.接口 3.比较自定义类型 学习不要眼高手低,...
HashMap源码分析 Java源码系列:下方连接 http://t.csdn.cn/Nwzed 文章目录...
route-resource-... route-resource-preload 🚀 专注于提升应用首屏加载速度...
房地产合同管理台账范本 房地产... 合同管理台账范本:   某房地产开发有限公司合同管理台账
关于房地产合同专员职责范本 房...  合同管理专员的岗位职责:  职责1  协助采购价格和合同管理体系的建立、完善和更新工作  职责2 ...
秋天的怀念读后感800字 多篇...   秋天的怀念读后感800字篇一:  《秋天的怀念》,讲述的是一个身患重病的母亲无微不至地关照着双腿...
关于技术改造合同范本2篇 设备...  技术改造合同范本一  技术改造借款合同  借款单位:_______________  地址:___...
关于房地产投资合同范文 房地产...  房地产投资合同范文一  甲 方:**公司  法定代表人:_____________________...
使用自签名证书的SSL (li... 配置服务器端mkdir -p /u01/app/oracle/walletorapki wallet...
Java设计模式 -05适配器... 适配器模式 一、现实生活中的适配器例子 泰国插座用的是两孔的(欧标)&#...
国际技术服务合同模板范本 技术... 国际技术服务合同模板一  甲方:________________  地址:_____________...
房地产合同管理台账范本 房地产... 合同管理台账范本:   某房地产开发有限公司合同管理台账
房地产顾问合同_房地产顾问合同... 房地产顾问合同范文1  根据《中华人民共和国律师暂行条例》第二条第一款的规定,______(简称甲方...
房地产项目合同_房地产项目合同... 房地产项目合同范文1  委托方:_______(以下简称甲方)  地址:_________  居间方...
房地产合作开发合同_房地产合作...  房地产合作开发合同范文1  甲方:   乙方:  根据《中华人名共和国合同法》及其他有关法律、法规...