连接池exception GetConnectionTimeoutException get/close not same thread

本文分析了在使用Druid连接池时遇到的GetConnectionTimeoutException异常,详细描述了问题现象及原因,最终通过调整Hibernate配置成功解决问题。

环境

hibernate 4.2.0.Final

spring 3.2.0.RELEASE

druid 1.0.2

 

异常信息

Caused by: org.hibernate.exception.GenericJDBCException: Could not open connection

Caused by: com.alibaba.druid.pool.GetConnectionTimeoutException: wait millis 60000, active 20

[WARN] 09-24 14:11:29 [pool.DruidDataSource.recycle:1132] get/close not same thread

 

现象分析,20个连接都用完后,就拿不到新连接,查不出数据了

 

 

 

网上查了下

 

http://www.oschina.net/question/1170760_127675  这里面的描述最像

 

kalo回复 @千纸鹤 : 是因为我用了Tomcat7,并且Spring里面使用了异步机制。造成了durid连接池打开关闭不是同一线程。 去掉spring对异步的支持,就不会出现这个问题。你可以在Tomcat6里面试试。 具体为什么 异步会造成durid连接池打开关闭不是同一线程,我也不清楚,还在研究。

 

 

 

这个和tomcat无关,tomcat6一样会出现。

 

解决方法

在spring的sessionFactory的配置中 <property name="hibernateProperties"> 加上

 

<prop key="hibernate.connection.release_mode">AFTER_TRANSACTION</prop>

 

问题就解决了。

 

 

2025-10-04 18:28:02,810 DEBUG com.sjsemi.rrc.interceptor.AuthorizeInterceptor - - 用户:[JE02787]访问URL:[/rrc/login.html]耗时[44685]ms 2025-10-04 18:28:38,545 DEBUG org.mybatis.spring.SqlSessionUtils - - Creating a new SqlSession 2025-10-04 18:29:06,782 DEBUG org.mybatis.spring.SqlSessionUtils - - Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@21047b8c] 2025-10-04 18:30:28,424 WARN com.alibaba.druid.pool.DruidDataSource - - get/close not same thread 2025-10-04 18:30:46,347 DEBUG org.mybatis.spring.transaction.SpringManagedTransaction - - JDBC Connection [oracle.jdbc.driver.T4CConnection@5729a452] will be managed by Spring 2025-10-04 18:31:23,510 DEBUG rrc.mybatis.mapper.MailNotificationMapper.findUnSendTopRecord - - ==> Preparing: SELECT ID, MAIL_RRC_NO, MAIL_SUBJECT, MAIL_CONTENT, MAIL_FROM, MAIL_TO, MAIL_CC, MAIL_BCC, MAIL_ATTACHMENTS, MAIL_CREAT_DATE, MAIL_CREAT_USER, MAIL_SEND_FLAG, MAIL_SEND_TIME, MAIL_FAB_SITE ,ROWNUM FROM TB_RRC_MAIL_NOTIFICATION WHERE (MAIL_SEND_FLAG IS NULL OR MAIL_SEND_FLAG !=‘1’) AND ROWNUM=1 ORDER BY MAIL_CREAT_DATE ASC 2025-10-04 18:32:15,161 DEBUG org.mybatis.spring.SqlSessionUtils - - Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@21047b8c] 2025-10-04 18:32:56,663 DEBUG org.mybatis.spring.SqlSessionUtils - - Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@21047b8c] 2025-10-04 18:33:10,288 DEBUG org.mybatis.spring.SqlSessionUtils - - Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@21047b8c] 2025-10-04 18:33:29,159 ERROR org.springframework.scheduling.support.TaskUtils$LoggingErrorHandler - - Unexpected error occurred in scheduled task. org.springframework.jdbc.UncategorizedSQLException: Error querying database. Cause: java.sql.SQLException: connection holder is null The error may exist in file [D:\Project\tomcat_rrc\webapps\rrc\WEB-INF\classes\com\sjsemi\rrc\mybatis\source\MailNotificationMapper.xml] The error may involve com.sjsemi.rrc.mybatis.mapper.MailNotificationMapper.findUnSendTopRecord The error occurred while executing a query SQL: SELECT ID, MAIL_RRC_NO, MAIL_SUBJECT, MAIL_CONTENT, MAIL_FROM, MAIL_TO, MAIL_CC, MAIL_BCC, MAIL_ATTACHMENTS, MAIL_CREAT_DATE, MAIL_CREAT_USER, MAIL_SEND_FLAG, MAIL_SEND_TIME, MAIL_FAB_SITE ,ROWNUM FROM TB_RRC_MAIL_NOTIFICATION WHERE (MAIL_SEND_FLAG IS NULL OR MAIL_SEND_FLAG !=‘1’) AND ROWNUM=1 ORDER BY MAIL_CREAT_DATE ASC Cause: java.sql.SQLException: connection holder is null 这段日志中,第一行用户登录花了40多秒,打电话问过他了,说那个时候点了登录后一直转圈,进不去。 然后后边日志开始报错,此时系统崩溃了,打不开
最新发布
10-12
你提供的日志非常关键,结合用户行为和系统表现,我们可以清晰地分析出问题的**根本原因、发生过程以及解决方案**。下面我将详细解释。 --- ### ✅ 回答问题:为什么登录耗时 44 秒?后续为何报错导致系统崩溃? #### 🔴 根本结论: > **数据库连接池资源耗尽或线程不安全使用,导致 `connection holder is null` 异常,引发定时任务失败,并可能连锁反应造成整个应用无响应(假死)**。 用户登录长时间转圈 + 定时任务执行 SQL 报错 `connection holder is null` + Druid 提示 `get/close not same thread` → 这些是典型的 **数据库连接泄漏或跨线程非法操作连接池资源** 的症状。 --- ### 🔍 分步解析日志 | 日志时间 | 内容 | 分析 | |--------|------|------| | `18:28:02,810` | 用户 [JE02787] 访问 `/rrc/login.html` 耗时 **44685ms (约45秒)** | 登录页面加载极慢,说明此时服务器已严重卡顿,可能是线程阻塞或数据库无响应 | | `18:28:38,545` | 创建新的 SqlSession | MyBatis 开始执行数据访问操作(可能是登录校验查询) | | `18:29:06,782` | 注册事务同步 SqlSession | Spring 正在管理事务中的数据库会话 | | `18:30:28,424` | **WARN: get/close not same thread** | ⚠️ **Druid 关键警告!获取连接和关闭连接不在同一个线程** → 极大概率存在 **线程不安全的操作** 或 **手动在线程间传递了 Connection/SqlSession** | | `18:30:46,347` | JDBC Connection 将由 Spring 管理 | 表明当前线程拿到了连接 | | `18:31:23,510` | 执行 `findUnSendTopRecord` 查询语句 | 定时任务正在尝试拉取未发送邮件记录 | | `18:32:15,161` | 释放事务性 SqlSession | 开始释放连接 | | `18:32:56,663` ~ `18:33:10,288` | 取消注册并关闭 SqlSession | 正常流程结束 | | `18:33:29,159` | **ERROR: Unexpected error in scheduled task**<br>`Cause: java.sql.SQLException: connection holder is null` | ❌ **致命错误!** Spring 无法找到当前线程绑定的数据库连接 → 说明事务或连接上下文丢失 | --- ### 🧨 问题产生的根本原因 #### 1. `connection holder is null` 是什么? - Spring 使用 `TransactionSynchronizationManager` 在每个线程中维护一个 `ThreadLocal<Map<Object, Object>>` 来保存当前线程的数据库连接(ConnectionHolder)。 - 当你在某个方法中需要访问数据库时,Spring 会从当前线程查找是否有可用的 ConnectionHolder。 - 如果找不到 → 抛出 `connection holder is null`。 👉 常见于以下情况: - 在子线程中调用了 DAO 方法(但父线程的事务不会自动传播到子线程) - 手动启用了异步任务(如 `@Async`),但没有配置事务传播 - 使用了线程池处理数据库操作,却未正确绑定连接 #### 2. `get/close not same thread` 是什么? 这是 **Druid 数据源检测到非同一线程获取与归还连接** 的警告。 例如: ```java Connection conn = dataSource.getConnection(); // Thread-A 获取 new Thread(() -> { dao.query(conn); // Thread-B 使用 conn.close(); // Thread-B 关闭 }).start(); ``` 虽然能运行,但 Druid 不允许这种跨线程使用,因为会导致连接状态混乱、泄漏等问题。 --- ### 💥 为什么会导致“系统崩溃打不开”? 当出现大量连接未正确释放或频繁创建销毁: - 数据库连接池被耗尽(默认 Druid 只有 8~20 个活跃连接) - 新请求无法获取数据库连接 → 请求阻塞等待 - Tomcat 线程池逐渐被占满(所有请求都在等 DB 连接) - 最终表现为:页面一直转圈、接口超时、500 错误、甚至完全无法访问 这就是你说的“系统崩溃了,打不开”的真实原因 —— 并非 JVM 崩溃,而是 **服务雪崩式瘫痪**。 --- ### ✅ 解决方案 #### ✅ 方案一:禁止跨线程使用数据库连接(最重要) 确保所有数据库操作都在**同一个线程内完成**,尤其是定时任务! 如果你用了类似这样的代码: ```java @Scheduled(fixedDelay = 5000) public void sendMailTask() { executorService.submit(() -> { // 子线程执行 MailNotification mail = mailNotificationService.findUnSendTopRecord(); // ... }); } ``` ❌ 错误!子线程拿不到主线程的事务上下文! ✅ 正确做法:要么让定时任务本身短小精悍直接执行;或者启用异步支持并手动管理连接。 推荐改造为: ```java @Scheduled(fixedDelay = 5000) public void sendMailTask() { try { MailNotification mail = mailNotificationService.findUnSendTopRecord(); if (mail != null) { // 发送邮件逻辑... mail.setMailSendFlag("1"); mailNotificationService.updateNotification(mail); } } catch (Exception e) { log.error("Failed to send mail in scheduled task", e); } } ``` > ✅ 所有操作都在主线程完成,避免跨线程问题。 --- #### ✅ 方案二:检查是否误用了 `@Async` 或自定义线程池 如果确实需要异步处理,请确保: - 不在异步方法中直接调用 Service - 或者开启作用域继承:使用 `InheritableThreadLocal` 或 Spring TaskExecutor 例如配置线程池支持上下文传递: ```java @Configuration @EnableAsync public class AsyncConfig { @Bean("taskExecutor") public Executor taskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(5); executor.setMaxPoolSize(10); executor.setThreadNamePrefix("async-thread-"); executor.setTaskDecorator(runnable -> { // 继承父线程的 MDC / TransactionContext 等 Map<String, String> contextMap = MDC.getCopyOfContextMap(); return () -> { try { MDC.setContextMap(contextMap); runnable.run(); } finally { MDC.clear(); } }; }); executor.initialize(); return executor; } } ``` 然后在异步方法上使用: ```java @Async("taskExecutor") public void asyncSendMail(MailNotification mail) { ... } ``` --- #### ✅ 方案三:优化 Druid 配置,防止连接泄漏 ```java spring.datasource.druid.initial-size=5 spring.datasource.druid.min-idle=5 spring.datasource.druid.max-active=20 spring.datasource.druid.max-wait=60000 spring.datasource.druid.time-between-eviction-runs-millis=60000 spring.datasource.druid.min-evictable-idle-time-millis=300000 # 开启 PSCache(提升性能) spring.datasource.druid.pool-prepared-statements=true spring.datasource.druid.max-pool-prepared-statement-per-connection-size=20 # 检测泄露(重要!) spring.datasource.druid.remove-abandoned=true spring.datasource.druid.remove-abandoned-timeout=180 spring.datasource.druid.log-abandoned=true ``` > 当连接使用超过 180 秒未关闭,Druid 会强制回收并打印堆栈帮助定位泄漏点。 --- #### ✅ 方案四:添加监控和告警 - 添加 Druid Monitor 页面:`/druid/index.html` 查看实时连接数、SQL 性能、慢查询等 - 设置 Prometheus + Grafana 监控连接池使用率 - 对慢 SQL 增加 AOP 日志拦截器 --- ### ✅ 如何验证修复效果? 1. 重启应用后观察日志是否还有 `get/close not same thread` 2. 查看 Druid 控制台中“活跃连接数”是否稳定,不超过最大值 3. 模拟高并发登录测试,确认不再出现长延迟 4. 使用 JMeter 测试定时任务高峰期表现 --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值