一次生产事故复盘服务报 “Failed to obtain JDBC Connection” 的完整排查与解决一、问题现象系统运行一段时间后开始频繁出现如下异常FailedtoobtainJDBCConnection;nested exception isjava.sql.SQLTransientConnectionException:swxx-Connectionis not available,request timed out after30000ms.表现为接口全部超时定时任务报错数据库连接获取失败服务响应变慢服务器 CPU 飙升接近 100%重启服务后短暂恢复运行一段时间再次复现。二、第一反应错误判断看到这个异常第一直觉是是不是数据库连接泄漏了于是我做了这些操作1️⃣ 开启连接泄漏检测remove-abandoned:trueremove-abandoned-timeout:180log-abandoned:true2️⃣ 开启连接保活test-while-idle:truevalidation-query:SELECT 1time-between-eviction-runs-millis:60000结果❌ 问题依旧存在❌ 没有明显泄漏日志三、进一步排查1️⃣ 查看数据库连接数showglobalstatuslikeThreads_connected;发现服务启动前241启动后271只增加了 30 个连接。说明不是连接无限增长型泄漏2️⃣ 查看正在执行的 SQLshowprocesslist;发现大量相同 SQL状态Sending data执行时间2000 秒同一条 SQL 被并发执行而且最长执行时间超过 40 分钟还未结束。问题开始清晰。四、真正原因核心问题不是连接泄漏。而是慢 SQL 长时间占用数据库连接导致连接池被耗尽。问题 SQL 结构核心片段andnot(dpgCASE...AND(SELECTSUM(t2.drp)FROMwxddz_r_202602 t2WHEREt2.adzt1.adzANDt2.ecdt1.ecdANDt2.tmDATE_SUB(t1.tm,INTERVAL24HOUR)ANDt2.tmt1.tm)CASE...)这是一个相关子查询Correlated Subquery含义是外层扫描一行内层子查询再执行一次如果外层扫描 10 万行 内层子查询就执行 10 万次 每次还可能扫描大量数据这是指数级性能灾难。五、连锁反应机制整个问题的完整链路是1️⃣ SQL 执行时间极长40分钟2️⃣ 连接一直被占用3️⃣ 连接池 maxActive 很小如 64️⃣ 新请求无法获取连接5️⃣ 30秒后超时6️⃣ 报 Failed to obtain JDBC Connection7️⃣ 由于数据库疯狂扫表8️⃣ CPU 被打满这不是连接不释放。而是连接被“合法地”长时间占用。六、为什么 CPU 会爆满MySQL 状态显示Sending data这通常意味着正在做大量数据扫描可能 using temporary可能 using filesort没有命中索引如果是全表扫描 子查询循环扫描数据库 CPU 会持续 100%。七、根本优化方案方案一加复合索引必要如果没有如下索引ALTERTABLEwxddz_r_202602ADDINDEXidx_adz_ecd_tm(adz,ecd,tm);子查询必定全表扫。这是第一步。方案二避免相关子查询相关子查询是性能杀手。优化思路先汇总 24 小时数据再 JOIN或改为分批处理或程序层面计算在 MySQL5.7 没有窗口函数的情况下可以改为临时汇总表。方案三分批处理原 SQLWHEREt1.id9062999ANDt1.id9076452如果数据量大建议分批 1000 条逐批计算控制并发不要一次性大范围扫描。方案四限制定时任务并发如果是 Quartz 任务DisallowConcurrentExecution避免同一任务并发执行。否则慢任务未完成新任务又启动。八、数据库参数是否有帮助比如max_connections400是否有用答案❌ 没本质帮助如果慢 SQL 不解决400 个连接 400 个慢查询数据库更快崩溃根因不在连接数。九、事故总结这次问题本质是规则计算型 SQL 被写成在线实时查询且使用相关子查询导致指数级性能问题最终表现为连接池耗尽JDBC 获取失败服务不可用CPU 爆满十、经验总结非常重要1️⃣ 出现 JDBC Connection is not available不要第一时间怀疑连接泄漏。先做showprocesslist;看是否有长时间 SQL。2️⃣ 相关子查询慎用结构像select...whereexists(select...)在大表中极易出问题。3️⃣ 定时任务必须控制并发慢任务叠加是灾难。4️⃣ 数据规则计算应尽量批处理预汇总离线计算不要在线复杂聚合。最终结论这次问题不是连接池问题。不是 max_connections 问题。不是连接泄漏问题。而是一条极慢 SQL 长时间占用连接导致连接池耗尽。当你看到Failed to obtain JDBC Connection请优先排查是否存在长时间执行的慢 SQL。