一次Oracle会话爆满的惊魂时刻:Spring Boot + MyBatis连接池配置救场
摘要某生产环境Oracle 11g数据库正常运行一段时间后PL/SQL突然无法连接应用后台报“连接超时”。DBA排查发现数据库会话数达到上限150紧急处理后恢复。本文详细复盘了整个排查过程从数据库参数、连接池配置、代码泄漏等多个维度抽丝剥茧最终定位到根本原因——默认HikariCP连接池配置过于保守且缺少泄漏检测并结合业务特点给出了生产级的连接池推荐配置。如果你也曾被“连接池爆满”困扰这篇文章或许能帮你少走弯路。1️⃣ 问题现象时间线Oracle 11g数据库运行一段时间后约数小时至数天新的数据库连接请求失败。具体表现PL/SQL Developer提示“监听无法访问”。Spring Boot应用后台报错Connection is not available, request timed out after 30000ms。DBA初步反馈数据库会话数已达上限原值150需要临时扩容或清理会话。临时措施DBA将sessions参数从150调大至500并重启数据库系统恢复正常。2️⃣ 环境与配置数据库Oracle 11g应用框架Spring Boot 2.x MyBatis连接池HikariCPSpring Boot默认驱动ojdbc7 (12.1.0.2)关键配置问题发生前spring:datasource:driver-class-name:oracle.jdbc.OracleDriverurl:jdbc:oracle:thin:182.10.100.117:1525:dbusername:xxxpassword:xxx# 未显式配置任何连接池参数数据库参数变更SHOWPARAMETER sessions;-- 原值150改为5003️⃣ 排查过程层层递进3.1 会话快照分析问题恢复后DBA在重启数据库后执行了以下查询SELECTMACHINE,PROGRAM,STATUS,LAST_CALL_ETFROMv$sessionWHEREUSERNAMEappuser;结果MACHINE PROGRAM STATUS LAST_CALL_ET localhost.localdomain JDBC Thin Client INACTIVE 1414 localhost.localdomain JDBC Thin Client INACTIVE 1414 ...共10条记录结论当前只有10个来自应用的会话全部空闲INACTIVE完全符合HikariCP默认最大连接数10。这说明问题发生时一定存在远超10个的额外会话但已经被重启清空。3.2 排除多应用/多实例干扰用户确认只有一个Spring Boot应用没有其他程序连接同一数据库。那么单应用如何用尽150个会话可能性指向应用代码存在连接泄漏且泄漏的连接绕过了连接池管理。3.3 排查代码中的“显式连接获取”全局搜索SqlSessionFactory.openSession()→未发现。全局搜索DriverManager.getConnection→未发现。使用MyBatis-Plus版本2.1.4稳定版无已知泄漏Bug。矛盾点如果所有数据库操作都通过MyBatis HikariCP那么最大连接数应被限制在10不可能达到150。除非……3.4 真正的元凶默认配置的“温柔陷阱”Spring Boot默认的HikariCP配置如下maximumPoolSize 10minimumIdle 10与最大值相同idleTimeout 60000010分钟maxLifetime 180000030分钟leakDetectionThreshold 0禁用泄漏检测这意味着连接池会始终保持10个活跃连接即使业务空闲也不释放。一旦代码中某处偶然创建了物理连接例如通过JNDI、原生JDBC、某些第三方库且忘记关闭这些连接不会被连接池管理就会永驻数据库直到会话数爆满。由于泄漏检测未开启应用日志中没有任何线索。推测的问题发生路径某次代码发布引入了一个小众场景比如调用存储过程返回游标、使用Oracle的某些专有API每次调用都新建一个Connection但未关闭。该场景触发频率不高每天泄漏几个连接运行数周后累积到150。数据库拒绝新连接应用报错DBA扩容重启问题暂时消失。4️⃣ 解决方案生产级推荐配置4.1 显式配置HikariCP并开启泄漏检测在application.yml中添加以下配置针对业务最长查询≤2分钟的场景spring:datasource:hikari:# 连接池大小根据实际并发调整建议20~50maximum-pool-size:20minimum-idle:5# 超时控制connection-timeout:30000idle-timeout:600000max-lifetime:1800000# 泄漏检测关键leak-detection-threshold:180000# 3分钟# 其他auto-commit:truevalidation-timeout:50004.2 参数解释参数推荐值理由maximum-pool-size20单应用10可能偏小20可应对中等并发且数据库会话上限500有足够余量。minimum-idle5保持少量空闲连接即可避免资源浪费。leak-detection-threshold1800003分钟业务最长查询≤2分钟设置3分钟可避免误报一旦连接被持有超过3分钟日志会打印堆栈精准定位泄漏代码。connection-timeout30秒连接池繁忙时最多等待30秒后快速失败避免线程阻塞。max-lifetime30分钟防止数据库或网络设备强制断开长期闲置的连接。4.3 为什么是3分钟正常查询最长2分钟加上网络、GC等抖动2分钟内应归还连接。设置3分钟提供了1分钟的缓冲既能捕获泄漏比如连接被持有4、5分钟又不会干扰正常业务。5️⃣ 监控与验证5.1 部署会话监控脚本使用Python脚本定期采集v$session数据脚本代码见附录观察user_sessions趋势稳定在maximum-pool-size附近 → 正常。持续缓慢增长 → 仍有泄漏需根据泄漏检测日志修复。5.2 观察应用日志配置生效后如果出现Connection leak detection: a connection was held for 180000 ms and not returned ...堆栈信息会直接指出哪一行代码获取了连接但未归还。5.3 压力测试在测试环境使用JMeter模拟高并发观察会话数是否受控。6️⃣ 总结与建议问题根本原因解决方案数据库会话被占满应用代码中存在连接泄漏 HikariCP默认关闭泄漏检测开启leak-detection-threshold修复泄漏代码单应用为何能突破最大连接数限制泄漏的连接绕过连接池如直接DriverManager.getConnection代码审查 统一使用Autowired DataSource重启后问题消失但复发重启清空了泄漏会话但源头未修复持续监控 配置检测 代码修复给开发者的三点忠告永远不要依赖默认配置显式设置连接池大小和泄漏检测阈值。任何获取Connection的地方都必须使用try-with-resources或finally关闭。定期查看数据库会话数一个简单的SELECT COUNT(*) FROM v$session WHERE TYPEUSER就能提前预警。附录简易会话监控脚本Pythonimportoracledbimporttimeimportcsvfromdatetimeimportdatetime# 配置数据库连接使用环境变量connoracledb.connect(useros.getenv(DB_USER),passwordos.getenv(DB_PWD),dsnhost:port/sid)cursorconn.cursor()whileTrue:cursor.execute(SELECT COUNT(*) FROM v$session WHERE TYPEUSER)countcursor.fetchone()[0]withopen(sessions.csv,a)asf:f.write(f{datetime.now()},{count}\n)time.sleep(300)# 每5分钟采集一次关键词Oracle会话爆满、Spring Boot连接池、HikariCP泄漏检测、MyBatis连接泄漏、生产故障复盘如果你也遇到过类似问题欢迎在评论区分享你的排查经历。技术路上我们一起避坑。