Druid连接池1.2.16版本的超时问题复盘
admin 发布于:2026-09-18 13:26:09
阅读:loading
事情是这样的,以前某个项目里面使用到了<artifactId>druid-spring-boot-starter</artifactId>的1.2.11版本,这不因为版本漏洞升级需要升级到1.2.16,想着都是小版本的小升级,闭眼直接升了,然而没想到的是升级出了问题了,特整理下细节。
那就升级版本吧,升级到1.2.16,考虑是较小版本的版本升级,没想那么多,所以出的问题就是:当一个SQL执行超过10秒后就报错了,提示超时问题(使用的是高斯数据库)......后面的情况跳过。在开发环境复现了一下,在Oracle库还真没有复现出来,在高斯数据库复现出来了,但是问题的解决并没有那么简单,以下几点尤为重要:
(1)前期连接池相关的配置没有设置超时时间,源码中的超时时间 socketTimeout = DEFAULT_TIME_SOCKET_TIMEOUT_MILLIS = 10秒,默认超时时间仅为10秒钟;
(2)1.2.16版本的设置 socketTimeout 超时时间存在内置的bug;
(3)同时设置了queryTimeout、transactionQueryTimeout、socketTimeout 三个超时时间;
所以,本次分享的重点就是来验证源码中的socketTimeout的超时时间bug,即便是设置了该参数,仍然不会起作用。
(1)创建一个验证的Service类LockDataService,提供两个函数,分别为:lock和update;
(2)lock方法的实现为查询一个表的一条数据,使用for update来锁定这条数据,同时使用Thread.sleep来休眠1分钟,模拟这个方法执行耗时为15秒钟;
(3)update方法的实现为修改这个表的同一条数据,直接修改;
(4)在spring boot项目中,在启动类Bootstrap的main函数里面直接调用两个方法,lock方法增加@Async线程异步执行,而后再执行update对这条已锁定的数据进行修改;
验证的细节在于lock方法执行15秒钟,事务一直没有提交(表示锁定着这条数据),而update方法在10秒内一直无法获取到数据锁,方法一直被阻塞,直到超过10秒时方法报错,注意了已经将socketTimeout设置成了60秒,所以最根本的原因是在于连接池初始化设置的socketTimeout为60秒没有生效,最终生效的仍然是框架中默认的10秒;
@Configuration
public class DruidDataSourceConfig {
@Bean
public DruidDataSource druidDataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:5528/chendd-blog?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8");
dataSource.setUsername("root");
dataSource.setPassword("******");
//设置超时时间60秒
dataSource.setQueryTimeout(60);
dataSource.setTransactionQueryTimeout(60);
//读取超时时间,60秒
dataSource.setSocketTimeout(60 * 1000);
return dataSource;
}
}@Service
public class LockDataService {
@Resource
private DataSource dataSource;
@Transactional
@Async
public void lock() {
JdbcTemplate jdbcTemplate = new JdbcTemplate(this.dataSource);
List<Map<String, Object>> dataMap = jdbcTemplate.queryForList("select * from sys_user t1 where t1.userId = 1 for update");
System.out.println(dataMap);
//假设执行需要 15 秒钟,模拟当前事务锁定数据
try {
TimeUnit.SECONDS.sleep(15L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
@Transactional
public void update() {
JdbcTemplate jdbcTemplate = new JdbcTemplate(this.dataSource);
jdbcTemplate.update("update sys_user t1 set t1.realName = '陈冬冬' where t1.userId = 1");
}
}public class Boostrap {
public static void main(String[] args) throws Exception {
ConfigurableApplicationContext applicationContext = SpringApplication.run(Boostrap.class, args);
DruidDataSource dataSource = (DruidDataSource) applicationContext.getBean(DruidDataSource.class);
System.out.println("连接池允许的SocketTimeout超时时间为:" + dataSource.getSocketTimeout() + " 毫秒");
//调用一个Service锁定一个表的一条数据5分钟
LockDataService lockDataService = applicationContext.getBean(LockDataService.class);
//线程异步,锁定数据
lockDataService.lock();
TimeUnit.SECONDS.sleep(2);
System.out.println("数据已锁定,尝试更新这条数据");
long begin = System.currentTimeMillis();
try {
//更改这条数据,查看超时时间
lockDataService.update();
System.out.println("执行成功,没有超时报错");
} catch (Exception e) {
System.out.println("执行失败,耗时:" + (System.currentTimeMillis() - begin));
} finally {
long end = System.currentTimeMillis();
if (end - begin > dataSource.getSocketTimeout()) {
System.err.println("不在超时允许的范围内,真正的超时报错");
} else {
System.out.println("在超时允许的范围内,验证是否有超时报错");
}
}
}
}
(1.2.16超时报错)

(1.2.21执行成功)
(1)版本升级由1.2.16再升级到1.2.21后,升级后代码执行正常,socketTimeout参数已生效;
(2)使用1.2.16版本时,在初始化DruidDataSource的方法中设置了socketTimeout参数,但是在验证类Bootstrap中获取到的连接池参数socketTimeout仍然为10秒,这属于Druid版本组件的问题;
(3)本次属于特定组件特定版本的历史版本问题,所述的相关组件版本已经为多年前的历史古老版本,应该拥抱更新更稳定的最新版本;
点赞
欢迎来到陈冬冬的学习天地 | 学习与经验整理分享平台