近期工作中遇到一个问题:数据库连接超时异常。项目架构是SSM+Boot。近期每天早上都有管理员反映后管登不上去了,并发来了截图
异常信息
### Error querying database. Cause: org.springframework.jdbc.CannotGetJdbcConnectionException: Failed to obtain JDBC Connection; nested exception is com.alibaba.druid.pool.GetConnectionTimeoutException: wait millis 60000, active 20, maxActive 20, creating 0
### The error may exist in URL [jar:file:/mall/jar/mall-admin-2.0-SNAPSHOT.jar!/BOOT-INF/lib/mall-mbg-1.0-SNAPSHOT.jar!/com/macro/mall/mapper/UmsAdminMapper.xml]
### The error may involve com.macro.mall.mapper.UmsAdminMapper.selectByExample
### The error occurred while executing a query
### Cause: org.springframework.jdbc.CannotGetJdbcConnectionException: Failed to obtain JDBC Connection; nested exception is com.alibaba.druid.pool.GetConnectionTimeoutException: wait millis 60000, active 600, maxActive 600, creating 0
at org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:30)
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:150)
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:141)
报错信息为在执行UmsAdminMapper下的selectByExample方法时连接超时。我仔细检查了SQL,确认无误,排除了SQL问题,然后去检查连接配置,都正常呀,连接数也没有超。于是便开始了面向百度,Google开发,搜了很多解决办法都不行。
问题分析
druid既然已经报错active 600, maxActive 600,肯定就是当前druid确实还有600个未释放的链接资源,但是通过show processlist查看实际连接确实没有600个,于是得出结论:有通过druid获取了连接,但是还未释放连接。未释放的连接在超过一定时间后,被mysql主动释放,所以服务实际存活的连接确实不足600个,但是mysql释放连接之后不会告诉druid,所以druid认为还是600个连接正在使用。
突然想到近期的一个新需要,需要从第三方调接口拉取第三方的商品,进行入库。因为数据量有点大,便直接采用了mybatis的批处理方式,创建sqlSession,指定BATCH模式。代码:
SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH, false);
ProductMapper mapper = session.getMapper(ProductMapper.class);
for (int i = 0; i < list.size(); i++) {
mapper.insertSelective(list.get(i));
//每1000条提交一次防止内存溢出
if (i % 1000 == 999) {
session.commit();
session.clearCache();
}
}
session.commit();
session.clearCache();
大家第一眼看去是不是没毛病呀,我写的时候也觉得没毛病,测试功能也正常,但是当我仔细看了一眼代码,发现个致命的错误。没有关闭连接。手动关闭连接这个操作已经很久没有用过了。每逢遇到需要释放资源的都使用 try-with-resources ,结果这次忘了这茬了。
所以这块代码每运行一次就会开启一个资源,然后没有释放,累计下来便达到了600。加上 try-with-resources之后,成功解决。
try-with-resources
java1.7 引入了 try-with-resources 声明,将 try-catch-finally 简化为 try-catch,这其实是一种语法糖,在编译时会进行转化为 try-catch-finally 语句。它要求在 try-with-resources 声明中定义的变量实现了 AutoCloseable 接口或者Closeable接口(Closeable是AutoCloseable 的子类),这样在系统可以自动调用它们的close方法,从而替代了finally中关闭资源的功能。