com.alibaba.druid.pool.GetConnectionTimeoutException: wait millis 60000, active 20, maxActive 20, creating 0
活动的连接数为20, 最大的连接数为20, 活动的连接数与最大连接数相同,连接池用完了,在等待60秒后,没有新连接可用,然后超时了。
stat监控页面显示,活跃连接数很高不释放。CPU超过100%。
当程序存在缺陷时,申请的连接忘记关闭,这时候,就存在连接泄漏了。
比如Connection connection = jdbcTemplate.getDataSource().getConnection(); 这样得到的连接spring不会再帮你关闭,你需要手动关闭。
1、properties配置:
#druid recycle Druid的连接回收机制
#超过时间限制是否回收
spring.datasource.druid.removeAbandoned = true
#超时时间;单位为秒。180秒=3分钟
spring.datasource.druid.removeAbandonedTimeout = 180
#关闭abanded连接时输出错误日志
spring.datasource.druid.logAbandoned = true
2、xml配置:
<!-- 超过时间限制是否回收 -->
<property name="removeAbandoned" value="true" />
<!-- 超时时间;单位为秒。180秒=3分钟 -->
<property name="removeAbandonedTimeout" value="180" />
<!-- 关闭abanded连接时输出错误日志 -->
<property name="logAbandoned" value="true" />
此配置项会影响性能,只在排查的时候打开,系统运行时最好关闭。
此项配日志会将连接泄漏位置打印出来,手动关闭泄露位置的连接就行了。
以下为错误示例就会造成连接泄漏:
public class Test5 {
@Autowired
private JdbcTemplate jdbcTemplate;
private void test() {
Connection connection = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
for (int i = 0; i < 2; i++) {
connection = jdbcTemplate.getDataSource().getConnection();
// 第一次使用connection
ps = connection.prepareStatement("select 1 from dual");
rs = ps.executeQuery();
while (rs.next()) {
System.out.println(rs.getString(1));
}
// 连接connection可以一直使用,避免频繁创建connection消耗资源
// ps、rs记得要关闭
rs.close();
ps.close();
// 第二次使用connection
ps = connection.prepareStatement("select 1 from dual");
rs = ps.executeQuery();
while (rs.next()) {
System.out.println(rs.getString(1));
}
// 连接connection可以一直使用,避免频繁创建connection消耗资源
// ps、rs记得要关闭
rs.close();
ps.close();
// 第三次使用connection
ps = connection.prepareStatement("select 1 from dual");
rs = ps.executeQuery();
while (rs.next()) {
System.out.println(rs.getString(1));
}
// 连接connection可以一直使用,避免频繁创建connection消耗资源
// ps、rs记得要关闭
rs.close();
ps.close();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
粗略一看没什么问题,finally最终一定会关闭连接,但是仔细看连接在for循环中打开的,实际打开了2个连接只关闭了一个,最终每调用一次该方法就会泄漏一次连接。将connection = jdbcTemplate.getDataSource().getConnection();放在循环外面就解决了。
错误示例二:
public class Test5 {
@Autowired
private JdbcTemplate jdbcTemplate;
private void test() {
Connection connection = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
connection = jdbcTemplate.getDataSource().getConnection();
ps = connection.prepareStatement(sql);
rs = ps.executeQuery();
while (rs.next()) {
if (rs.getInt(1) == 1) {
test();// 递归
}
}
rs.close();
ps.close();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
递归也可能会导致,连接数不够。当递归函数需要递归很多次,只有递归结束才开始关闭连接,这期间活跃连接数陡增,若果这个函数被并发调用更恐怖。正确的做法,将connection作为递归函数的参数传递进去。
// isClosed()即使断开连接也会返回false,只有close()后才返回true。
// isValid(1)单位秒,连接失效返回false。
if (connection == null || connection.isClosed() || !connection.isValid(1)) {
connection = jdbcTemplate.getDataSource().getConnection();
}
一般一个connection可以创建300个preparedstatement同时使用,一个connection最大并行299个preparedstatement,记住preparedstatement用完后要关闭,connection每创建一个preparedstatement就相当于打开一个游标,超过300个就会报connection打开游标超出最大数(ORA-01000: 超出打开游标的最大数)。每个线程的preparedstatement应该是并行运行的。
所以体现出来spring管理connection连接的好处,不用去关心连接是否已经手动关闭。当项目中大量使用手动维护connection连接时,成千上万代码就会难免忘记关闭connection造成泄漏。
druid 配置参考 : DruidDataSource配置 · alibaba/druid Wiki · GitHub阿里云计算平台DataWorks(https://help.aliyun.com/document_detail/137663.html) 团队出品,为监控而生的数据库连接池 - DruidDataSource配置 · alibaba/druid Wikihttps://github.com/alibaba/druid/wiki/druiddatasource%E9%85%8D%E7%BD%AE