part5-connection-validation-tuning
约 3219 字大约 11 分钟
2026-05-24
系列文章:《深入Spring事务与数据源:从注解到数据库连接的全链路解析》
系列二第5篇:Druid连接有效性检测与性能调优完整指南
基于版本:Druid 1.2.x、Spring Framework 5.3.x
前置阅读:系列二第4篇:Druid Filter链机制
前言
终于到了系列二的最后一篇!
前面4篇文章,咱们把Druid的架构设计、连接管理、Filter链都搞清楚了。今天这篇文章,咱们来聊聊两个特别实用的话题:
- 连接有效性检测:怎么保证连接是可用的?
- 性能调优:怎么配置参数才能让Druid发挥最佳性能?
说实话,很多开发者用Druid用了好几年,就知道配个URL、用户名、密码,其他的参数全都是默认值。但是,如果你不懂这些参数的含义,不懂连接检测机制,系统出了问题的时候,你就只能干瞪眼!
而且我敢说,看完这篇文章,你不仅能成为Druid配置专家,还能举一反三,理解所有连接池的调优思路!
好了,废话不多说,开干!
一、回顾一下:Spring Boot是怎么配置Druid的?
老规矩,快速回顾一下配置。
核心入口:DruidDataSource配置
spring:
datasource:
druid:
url: jdbc:mysql://localhost:3306/test
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
# 连接池配置
initial-size: 5
max-active: 20
min-idle: 5
max-wait: 60000
# 连接检测配置(今天重点讲)
test-while-idle: true
test-on-borrow: false
test-on-return: false
validation-query: SELECT 1
validation-query-timeout: 1
# 保活配置
keep-alive: true
keep-alive-between-time-millis: 60000
# 超时配置
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
max-evictable-idle-time-millis: 900000好了,现在来看看这些配置都是啥意思,以及连接检测是怎么工作的!
二、为什么需要连接有效性检测?
在深入源码之前,咱们先搞清楚一个问题——为什么需要检测连接的有效性?
你可能觉得:"连接池里的连接,不都是好好的吗?干嘛还要检测?"
嘿,还真不一定!连接可能会因为以下原因失效:
- 数据库重启:数据库重启后,旧的连接都失效了
- 网络中断:网络抖动导致连接断开
- 防火墙超时:防火墙长时间不活动会断开连接
- MySQL超时:MySQL的
wait_timeout默认8小时,超时后断开连接 - 连接老化:连接长时间使用后可能出现异常
如果用了失效的连接,就会抛异常:
com.mysql.cj.jdbc.exceptions.CommunicationsException:
Communications link failure
The last packet successfully received from the server was 10000 milliseconds ago.所以,必须定期检测连接的有效性!
三、三种检测时机
Druid提供了三种检测时机:
核心机制1:testOnBorrow(获取时检测)
test-on-borrow: true每次从连接池获取连接时,都检测一下连接是否有效。
优点:最安全,保证获取到的连接一定有效
缺点:性能差,每次获取都要执行一次SELECT 1
核心机制2:testOnReturn(归还时检测)
test-on-return: true每次归还连接到连接池时,检测一下连接是否有效。
优点:保证归还的连接有效
缺点:性能差,而且意义不大(反正马上又要检测)
核心机制3:testWhileIdle(空闲时检测)
test-while-idle: true
time-between-eviction-runs-millis: 60000 # 每60秒检测一次后台线程定期检测空闲连接的有效性。
优点:性能好,不影响获取/归还连接
缺点:可能获取到刚失效的连接(概率很低)
扩展场景1:推荐配置是什么?
# 推荐配置
test-on-borrow: false # 关闭获取时检测
test-on-return: false # 关闭归还时检测
test-while-idle: true # 开启空闲时检测
time-between-eviction-runs-millis: 60000 # 每60秒检测一次这种配置性能最好,安全性也够用!
四、testOnBorrow:获取时检测的实现
好,现在来看看源码是怎么实现的。
核心方法1:getConnection中的检测
文件位置:druid/src/main/java/com/alibaba/druid/pool/DruidDataSource.java
@Override
public DruidPooledConnection getConnection(long maxWaitMillis) throws SQLException {
// ... 获取连接
DruidConnectionHolder holder = pollLast(nanos);
// ========== 检测连接有效性 ==========
if (testOnBorrow) {
// ★★★★★ 核心:验证连接是否有效 ★★★★★
boolean validate = testConnectionInternal(holder.getConnection());
if (!validate) {
// 连接无效,丢弃
discardConnection(holder);
// 重新获取连接
return getConnection(maxWaitMillis);
}
}
// 返回连接
return new DruidPooledConnection(holder);
}逻辑很简单:
- 检查
testOnBorrow配置 - 调用
testConnectionInternal()验证连接 - 如果无效,丢弃并重新获取
核心方法2:testConnectionInternal
文件位置:druid/src/main/java/com/alibaba/druid/pool/DruidDataSource.java
protected boolean testConnectionInternal(Connection conn) {
// ========== 方式1:使用validationQuery ==========
if (validationQuery != null) {
Statement stmt = null;
ResultSet rs = null;
try {
// 创建Statement
stmt = conn.createStatement();
// 设置超时时间
if (validationQueryTimeout > 0) {
stmt.setQueryTimeout(validationQueryTimeout);
}
// ★★★★★ 核心:执行validationQuery ★★★★★
rs = stmt.executeQuery(validationQuery);
// 如果执行成功,说明连接有效
return true;
} catch (SQLException e) {
// 执行失败,说明连接无效
return false;
} finally {
JdbcUtils.close(rs);
JdbcUtils.close(stmt);
}
}
// ========== 方式2:使用isValid()方法(JDBC 4.0+)==========
try {
return conn.isValid(validationQueryTimeout);
} catch (SQLException e) {
return false;
}
}验证连接有两种方式:
- 执行validationQuery(比如
SELECT 1) - 调用
Connection.isValid()(JDBC 4.0+提供的方法)
扩展场景2:validationQuery应该配置什么?
不同数据库的validationQuery不一样:
# MySQL
validation-query: SELECT 1
# Oracle
validation-query: SELECT 1 FROM DUAL
# PostgreSQL
validation-query: SELECT 1
# SQL Server
validation-query: SELECT 1
# Druid智能检测(推荐)
# Druid会根据URL自动识别数据库,设置对应的validationQuery扩展场景3:为什么推荐isValid()而不是validationQuery?
// 方式1:validationQuery
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT 1");
// 需要创建Statement、ResultSet,开销大
// 方式2:isValid()
conn.isValid(1);
// JDBC驱动内部实现,性能更好isValid()是JDBC 4.0+提供的标准方法,由数据库驱动实现,性能更好。
五、testWhileIdle:空闲时检测的实现
好,现在来看最常用的检测方式——空闲时检测。
核心机制:DestroyConnectionThread后台线程
文件位置:druid/src/main/java/com/alibaba/druid/pool/DruidDataSource.java
private class DestroyConnectionThread extends Thread {
public void run() {
while (true) {
try {
// ========== 第一步:定期执行 ==========
// 每timeBetweenEvictionRunsMillis毫秒执行一次
Thread.sleep(timeBetweenEvictionRunsMillis);
// ========== 第二步:检测空闲连接 ==========
if (testWhileIdle) {
// ★★★★★ 核心:调用检测逻辑 ★★★★★
checkIdleConnections();
}
} catch (InterruptedException e) {
break;
}
}
}
}
/**
* 检测空闲连接
*/
private void checkIdleConnections() {
List<DruidConnectionHolder> evictList = new ArrayList<>();
lock.lock();
try {
// 遍历所有空闲连接
for (int i = 0; i < poolingCount; ++i) {
DruidConnectionHolder holder = connections[i];
// 检查连接是否空闲超时
long idleMillis = System.currentTimeMillis() - holder.getLastActiveTime();
if (idleMillis >= timeBetweenEvictionRunsMillis) {
// ★★★★★ 核心:验证连接有效性 ★★★★★
boolean validate = testConnectionInternal(holder.getConnection());
if (!validate) {
// 连接无效,标记为待回收
evictList.add(holder);
}
}
}
// 移除无效连接
for (DruidConnectionHolder holder : evictList) {
// 从连接池移除
removeConnection(holder);
// 关闭连接
JdbcUtils.close(holder.getConnection());
}
} finally {
lock.unlock();
}
}逻辑是这样的:
- 后台线程定期执行(每60秒)
- 遍历所有空闲连接
- 检测连接有效性
- 移除无效连接
关键点:只检测空闲连接,不影响正在使用的连接。
六、keepAlive:连接保活机制
除了检测连接有效性,Druid还提供了保活机制——定期发送心跳,防止连接被数据库或防火墙断开。
核心机制:KeepAlive线程
文件位置:druid/src/main/java/com/alibaba/druid/pool/DruidDataSource.java
private class CreateConnectionThread extends Thread {
public void run() {
while (true) {
try {
// 等待
Thread.sleep(keepAliveBetweenTimeMillis);
// ========== 保活逻辑 ==========
if (keepAlive) {
keepAliveConnections();
}
} catch (InterruptedException e) {
break;
}
}
}
}
/**
* 保活连接
*/
private void keepAliveConnections() {
lock.lock();
try {
// 遍历所有空闲连接
for (int i = 0; i < poolingCount; ++i) {
DruidConnectionHolder holder = connections[i];
long idleMillis = System.currentTimeMillis() - holder.getLastActiveTime();
// 如果空闲时间超过keepAliveBetweenTimeMillis
if (idleMillis >= keepAliveBetweenTimeMillis) {
// ★★★★★ 核心:执行validationQuery,发送心跳 ★★★★★
boolean validate = testConnectionInternal(holder.getConnection());
if (validate) {
// 更新最后活跃时间
holder.setLastActiveTime(System.currentTimeMillis());
} else {
// 连接无效,关闭
JdbcUtils.close(holder.getConnection());
removeConnection(holder);
}
}
}
} finally {
lock.unlock();
}
}保活机制的逻辑是:
- 定期检查空闲连接
- 执行validationQuery(相当于发送心跳)
- 更新最后活跃时间
这样,连接就不会因为长时间不活动被断开了。
扩展场景4:keepAlive和testWhileIdle的区别
keepAlive(保活):
→ 定期执行validationQuery,发送心跳
→ 防止连接被数据库/防火墙断开
→ 保持连接活跃
testWhileIdle(空闲检测):
→ 定期检测连接有效性
→ 移除无效连接
→ 保证连接池中的连接都有效
两者配合使用效果最佳!扩展场景5:推荐配置
# 推荐配置
keep-alive: true
keep-alive-between-time-millis: 60000 # 每60秒保活一次
test-while-idle: true
time-between-eviction-runs-millis: 60000 # 每60秒检测一次
validation-query: SELECT 1
validation-query-timeout: 1 # 超时1秒七、连接池性能调优
好了,连接检测机制搞清楚了,现在来聊聊性能调优。
7.1 核心参数调优
参数1:initialSize(初始连接数)
# 低并发系统
initial-size: 5
# 高并发系统
initial-size: 20
# 推荐:根据系统负载调整
initial-size: 10参数2:maxActive(最大连接数)
# 低并发系统
max-active: 10
# 高并发系统
max-active: 50
# 超高并发系统
max-active: 100
# 推荐:根据数据库连接数限制调整
max-active: 20注意:maxActive不能超过数据库的最大连接数!
-- MySQL查看最大连接数
SHOW VARIABLES LIKE 'max_connections';
-- 一般MySQL默认151,建议maxActive设置为50-100参数3:minIdle(最小空闲连接数)
# 推荐:和initialSize保持一致
min-idle: 10参数4:maxWait(最大等待时间)
# 快速失败(推荐)
max-wait: 5000 # 5秒
# 耐心等待
max-wait: 60000 # 60秒
# 不等待
max-wait: 0 # 立即失败7.2 检测参数调优
参数5:testOnBorrow
# 推荐:false(性能好)
test-on-borrow: false
# 如果对连接可靠性要求极高
test-on-borrow: true参数6:testWhileIdle
# 推荐:true
test-while-idle: true
# 检测间隔
time-between-eviction-runs-millis: 60000 # 60秒参数7:keepAlive
# 推荐:true
keep-alive: true
# 保活间隔
keep-alive-between-time-millis: 60000 # 60秒7.3 完整推荐配置
spring:
datasource:
druid:
url: jdbc:mysql://localhost:3306/test
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
# 连接池配置
initial-size: 10
max-active: 20
min-idle: 10
max-wait: 5000
# 连接检测
test-on-borrow: false
test-on-return: false
test-while-idle: true
time-between-eviction-runs-millis: 60000
validation-query: SELECT 1
validation-query-timeout: 1
# 保活
keep-alive: true
keep-alive-between-time-millis: 60000
# 连接泄漏检测
remove-abandoned: true
remove-abandoned-timeout: 180
log-abandoned: true
# Filter
filters: stat,wall
# 监控
stat:
slow-sql-millis: 2000
log-slow-sql: true八、监控与排查
Druid提供了强大的监控功能,可以帮助你排查问题。
扩展场景6:如何访问监控页面?
spring:
datasource:
druid:
stat-view-servlet:
enabled: true
url-pattern: /druid/*
login-username: admin
login-password: admin123访问:http://localhost:8080/druid/index.html
扩展场景7:常见监控指标
数据源监控:
- 活跃连接数(ActiveCount)
- 空闲连接数(PoolingCount)
- 等待线程数(WaitThreadCount)
- 连接创建次数(CreateCount)
- 连接销毁次数(DestroyCount)
SQL监控:
- SQL执行次数
- SQL执行耗时
- 慢SQL列表
- SQL错误次数扩展场景8:如何排查连接泄漏?
# 开启泄漏检测
remove-abandoned: true
remove-abandoned-timeout: 180 # 3分钟
log-abandoned: true日志会输出:
abandon connection, owner thread: http-nio-8080-exec-1
ownerThread current stack:
at com.example.UserService.createUser(UserService.java:20)
at com.example.OrderService.createOrder(OrderService.java:30)这样就能定位到是哪个方法没有关闭连接!
九、系列二总结
通过这5篇文章,咱们完整追踪了Druid连接池的全链路:
第1篇:DataSource接口规范
- DataSource接口定义
- SPI机制
- 为什么需要连接池
第2篇:Druid连接池架构
- DruidDataSource整体架构
- 核心数据结构
- 初始化流程
- 后台线程
第3篇:连接获取与回收
- getConnection()完整流程
- pollLast()核心逻辑
- recycle()归还逻辑
- LIFO策略
- 泄漏检测
第4篇:Filter链机制
- Filter接口设计
- FilterChain责任链
- StatFilter统计监控
- WallFilter SQL防火墙
第5篇:连接有效性检测与性能调优
- 三种检测时机
- testOnBorrow实现
- testWhileIdle实现
- keepAlive保活机制
- 性能调优最佳实践
- 监控与排查
整个Druid连接池的核心就是:
1. 初始化连接池,创建初始连接
2. 获取连接时,从池中获取(LIFO)
3. 使用连接,执行SQL
4. 归还连接,放回池中
5. 后台线程定期检测连接有效性
6. Filter链拦截所有操作,做统计和监控十、系列三预告:MyBatis执行器
系列二到这里就圆满结束了!但是,咱们还有一个关键问题没深入:
MyBatis是怎么使用连接的?
- MyBatis的SqlSessionFactory是怎么创建的?
- Executor执行器是怎么工作的?
- StatementHandler是怎么执行SQL的?
- TypeHandler是怎么做类型映射的?
- ResultSetHandler是怎么处理结果集的?
这些问题,将在下一个系列《MyBatis执行器全链路源码解析》中为你揭晓。到时候咱们会深入到MyBatis的源码,看看它是怎么封装JDBC,怎么实现ORM的。
敬请期待系列三!
参考资料
- Druid 1.2.x 源码
src/main/java/com/alibaba/druid/pool/DruidDataSource.javasrc/main/java/com/alibaba/druid/filter/stat/StatFilter.java
- 《阿里巴巴Druid官方文档》
- 《MySQL技术内幕》- 连接管理章节
- 《Java性能调优实战》