资讯动态

项目程序运行一段时间就报错:超出打开游标的最大数(maximum open cursors exceeded)——用 TaoToken 统一 Key 通道排查 ORA-01000 的 JDBC 连接与

发布时间:2026/10/2 11:46:06 来源:尧图企业网站定制
1. 为什么程序跑几天才炸ORA-01000 的真实触发链路java.sql.SQLException: ORA-01000: 超出打开游标的最大数maximum open cursors exceeded这个报错最迷惑人的地方在于它不在你刚部署时出现而是程序稳定跑上几天甚至一周后才突然爆发重启之后又一切正常。很多人第一反应是去翻 SQL 语法结果把报错里最后打印的那条select * from TB_DIAGNOSTICS where ...单独拿到客户端执行完全没问题。于是就开始怀疑数据库游标数太小跑去改open_cursors参数。我先把结论摆在这里ORA-01000 在绝大多数 Java Web 项目里根因不是数据库参数小而是 JDBC 的Connection、Statement、PreparedStatement、ResultSet这四类对象有泄漏没有在 finally 里关闭或者只关了一半。Oracle 每执行一次查询就会在会话上打开一个游标游标是会话级资源只有显式 close 或者会话断开才会释放。你的接口被外部系统每隔几分钟调一次每次泄漏一两个游标几天下来单个会话累积到几百个超过open_cursors上限下一次查询就直接抛 ORA-01000。这个场景特别典型一个用 Maven 写的 Spring MVC 小接口对外提供历史数据查询调用方是定时任务或者第三方采集平台。接口本身逻辑简单就是拼 SQL 查 Oracle但代码里Connection是方法内DriverManager.getConnection拿的ResultSet遍历完就 return 了finally块里只关了Connection没关Statement或者干脆连Connection都没关。这种写法在本地测试时因为调用量小、JVM 重启频繁根本暴露不出来一上生产就原形毕露。排查这件事有三条线要同时走第一条是代码层的资源关闭审计重点看所有executeQuery、execute调用点第二条是连接池配置看maxActive、maxIdle、validationQuery是否合理连接池如果本身在泄漏连接游标也会跟着涨第三条是数据库侧的游标占用查询用 SQL 直接看当前哪个会话、哪条 SQL 占了多少游标这是定位泄漏点最快的证据。而在这个过程中如果你需要频繁地让 AI 帮你分析堆栈、生成排查脚本、对比连接池配置用 TaoToken 统一 Key 通道会省很多事。它把模型调用收敛到一个 API 入口你不用在多个平台之间来回切 Key排查思路可以连续对话下去。下面我会把三条线的具体操作、可复制的配置片段、游标查询 SQL 全部给出来你可以直接跟着做。2. 用 TaoToken 统一 Key 通道搭建排查环境排查 ORA-01000 这件事本质上是一个「读堆栈 → 定位代码 → 改配置 → 验证」的循环。你可能会反复让 AI 帮你做几件事解析那段几十行的 Oracle 异常堆栈找出真正抛错的业务方法根据你的连接池类型Druid、HikariCP、DBCP生成对应的配置模板写一段游标占用查询 SQL 并解释每一列含义最后帮你 review 修复后的finally块写得对不对。这些请求如果分散在好几个平台Key 管理会很乱上下文也接不上。TaoToken 在这里的角色是一个统一的模型调用通道。你只需要在官网 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 注册后拿到一个 Key然后在控制台创建 API Key就可以用同一个 Base URL 和 Key 去调用不同的模型。对于排查类工作我建议把「堆栈分析」和「配置生成」分开用不同模型跑前者用擅长长文本推理的后者用擅长代码生成的但 Key 是同一个切换成本几乎为零。具体操作路径是这样的先访问官网进入控制台 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 创建 API Key然后在 API Keys 页面 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 复制你的 Key。如果你只是想先验证模型能不能正常返回可以直接用模型对话页面 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 发一条消息测试。API 的基础地址是 https://taotoken.net/api注意这个地址不带任何查询参数直接作为 OpenAI 兼容的 base_url 使用。这里要强调一点TaoToken 是模型调用通道不是数据库连接工具也不是替代你 IDE 的东西。它的作用是让你在排查过程中有一个稳定的 AI 助手入口帮你更快地读懂报错、写出正确的关闭逻辑。真正的游标泄漏修复还是要落到你的 Java 代码和连接池配置上。对于长期要做这类排查和编码工作的同学可以考虑 Coding Plan https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 它更适合持续性的代码分析和 Agent 式排查不用每次单独计费。如果你用的是 Claude Code 这类工具接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 里面有完整的 Base URL、Key、Model ID 三件套配置说明。拿到 Key 之后你可以先用一个最简单的 curl 验证通道是否通curl https://taotoken.net/api/v1/chat/completions \ -H Content-Type: application/json \ -H Authorization: Bearer sk-你的Key \ -d { model: gpt-4o-mini, messages: [ {role: user, content: ORA-01000 在 JDBC 场景下最常见的三个原因是什么每个用一句话说明} ] }如果返回正常的 JSON说明通道没问题。接下来你就可以把那段 ORA-01000 的完整堆栈贴进去让模型帮你逐层分析。实测下来把堆栈里at io.swagger.api.DbCon.QueryInstrumentData1(DbCon.java:731)这一行单独拎出来问「这一行说明什么」比整段贴进去效果更好因为模型能直接聚焦到业务代码的调用点。3. 可复制的连接池与游标上限配置片段排查到这一步你需要两份配置一份是 Java 侧的连接池配置确保连接不会泄漏、游标能随连接归还而释放另一份是 Oracle 侧的游标上限参数作为兜底保护。我按最常见的三种连接池分别给出可复制的片段你根据自己的项目选一个。先说 Druid这是国内 Java 项目用得最多的。关键参数是maxActive、maxWait、validationQuery、removeAbandoned和removeAbandonedTimeout。removeAbandoned这个开关很重要它能在连接被借出超过指定时间后强制回收对于那种「忘了关连接」的代码是一道保险。配置放在application.yml里spring: datasource: druid: url: jdbc:oracle:thin://10.0.0.12:1521/ORCLPDB1 username: app_user password: your_password driver-class-name: oracle.jdbc.OracleDriver initial-size: 5 min-idle: 5 max-active: 20 max-wait: 60000 validation-query: SELECT 1 FROM DUAL test-while-idle: true test-on-borrow: false test-on-return: false time-between-eviction-runs-millis: 60000 min-evictable-idle-time-millis: 300000 remove-abandoned: true remove-abandoned-timeout: 180 log-abandoned: trueremove-abandoned-timeout: 180表示连接借出超过 180 秒还没归还就强制回收同时打日志。这个日志会告诉你哪个方法借了连接没还是定位泄漏点的直接线索。如果你用的是 HikariCP配置风格不一样它没有removeAbandoned但有一个leakDetectionThreshold单位是毫秒超过这个时间没归还连接就打印泄漏堆栈spring: datasource: hikari: jdbc-url: jdbc:oracle:thin://10.0.0.12:1521/ORCLPDB1 username: app_user password: your_password driver-class-name: oracle.jdbc.OracleDriver maximum-pool-size: 20 minimum-idle: 5 connection-timeout: 30000 idle-timeout: 600000 max-lifetime: 1800000 leak-detection-threshold: 60000 connection-test-query: SELECT 1 FROM DUALleak-detection-threshold: 60000表示连接借出超过 60 秒没还就打印堆栈这个堆栈会直接指向你的业务代码行号比什么都管用。再说 Oracle 侧的游标上限。默认open_cursors通常是 50 或 300对于并发不高的接口够用但如果你的代码确实有轻微泄漏调大一点能争取排查时间。查看当前值show parameter open_cursors;修改需要 DBA 权限且只对新会话生效alter system set open_cursors 1000 scope both;注意scope both会同时改内存和 spfile重启后仍生效。但我要提醒一句调大open_cursors只是缓解症状不是修复。如果你的代码每次请求泄漏一个游标调到 1000 也只是把爆炸时间从 3 天推迟到 60 天根本问题还在。最后是 JDBC 代码层的正确关闭模板这是最核心的。用 try-with-resources 写法Connection、PreparedStatement、ResultSet都会自动关闭顺序是 ResultSet → Statement → Connectionpublic ListDiagnostics queryByStation(String station, String start, String end) { String sql select * from TB_DIAGNOSTICS where DIG_STATION ? and DIG_DateTime to_date(?, yyyy-mm-dd hh24:mi:ss) and DIG_DateTime to_date(?, yyyy-mm-dd hh24:mi:ss); ListDiagnostics list new ArrayList(); try (Connection conn dataSource.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, station); ps.setString(2, start); ps.setString(3, end); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { Diagnostics d new Diagnostics(); d.setStation(rs.getString(DIG_STATION)); d.setDateTime(rs.getTimestamp(DIG_DateTime)); list.add(d); } } } catch (SQLException e) { log.error(queryByStation failed, station{}, station, e); throw new RuntimeException(e); } return list; }如果你还在用老的finally写法务必确认三层都关了而且关闭顺序不能反。很多人只关了Connection以为Statement会跟着关实际上 Oracle JDBC 驱动里Connection.close()确实会释放该连接上的所有游标但前提是这个Connection真的被 close 了。如果Connection是从连接池借的你只 close 了ResultSet没 closeConnection连接不归还游标就一直挂在这个会话上。4. 验证请求与游标占用查询确认修复真的生效改完代码和配置怎么确认游标不再泄漏不能只看「程序不报错了」因为报错是累积到上限才触发的你要在它触发之前就看到游标数在下降。这里给你两条验证路径一条是数据库侧的游标占用查询一条是通过 TaoToken 通道让模型帮你写压测脚本复现。先看数据库侧。Oracle 里查当前各会话的游标占用最直接的 SQL 是查v$open_cursorselect s.sid, s.serial#, s.username, s.program, s.status, count(o.cursor_type) as cursor_count from v$open_cursor o join v$session s on o.sid s.sid group by s.sid, s.serial#, s.username, s.program, s.status order by cursor_count desc;这条 SQL 会列出每个会话打开的游标数量按数量降序。你重点看username是你应用账号、program是 JDBC Thin Client 的那些行。如果修复前某个会话游标数是 400 多修复后稳定在 10 以内说明泄漏堵住了。再细一点看具体是哪些 SQL 在占游标select o.sid, o.cursor_type, o.sql_text, count(*) over (partition by o.sid) as total_per_sid from v$open_cursor o where o.sid in ( select sid from v$session where username APP_USER ) order by o.sid, o.cursor_type;这条能告诉你同一个会话里是不是同一条 SQL 被反复打开了几百次。如果是那基本可以确定是某个循环里每次迭代都新建PreparedStatement却没关。还有一个更宏观的视图看当前数据库整体的游标使用率select resource_name, current_utilization, max_utilization, limit_value from v$resource_limit where resource_name in (open_cursors, processes, sessions);current_utilization是当前打开的游标数limit_value是上限。修复前这个值会随着时间单调上升修复后应该在一个区间内波动不会持续爬升。数据库侧看完再用 TaoToken 通道做一次代码级验证。你可以把修复后的queryByStation方法贴给模型让它帮你生成一个 JMeter 或简单的 Java 循环压测脚本模拟 500 次调用然后观察游标数变化。请求示例curl https://taotoken.net/api/v1/chat/completions \ -H Content-Type: application/json \ -H Authorization: Bearer sk-你的Key \ -d { model: gpt-4o, messages: [ {role: user, content: 下面是一个修复后的 JDBC 查询方法请帮我写一个 Java main 方法循环调用它 500 次每次间隔 100ms并在每次调用后打印当前线程名。方法签名public ListDiagnostics queryByStation(String station, String start, String end)} ] }拿到生成的压测代码后跑一遍同时在数据库侧反复执行上面的v$open_cursor查询。如果 500 次调用结束后应用账号的游标数回落到个位数说明每次调用的游标都被正确释放了。这个验证比单纯「等几天看报不报错」快得多也可靠得多。如果你用的是 Claude Code 做代码审查接入方式在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 里有说明Base URL 填https://taotoken.net/apiKey 填你创建的 KeyModel ID 按文档里列出的填。这样你可以在编辑器里直接让模型 review 你的finally块不用来回切窗口。5. 本篇常见错排查401、local proxy failed、reading choices、OAuth在排查 ORA-01000 的过程中你可能会同时遇到两类报错一类是数据库侧的一类是调用 TaoToken 通道时的。我把最常见的几个列出来对照着看。401 Unauthorized。这个通常出现在你调 TaoToken API 时Key 没填对或者格式不对。检查两点一是 Key 是否完整复制有没有多空格二是请求头是不是Authorization: Bearer sk-xxx注意Bearer后面有一个空格。如果你是在 Claude Code 里配置确认ANTHROPIC_AUTH_TOKEN或对应的环境变量名和文档一致。401 和数据库无关纯粹是鉴权问题。local proxy failed。这个报错一般出现在你本地有网络代理设置但代理没有正确转发请求。注意这里说的是你本地开发环境的网络配置问题不是让你去搭什么代理。解决办法是检查你的 HTTP_PROXY / HTTPS_PROXY 环境变量如果不需要就清掉让请求直连。TaoToken 的 API 地址https://taotoken.net/api是标准 HTTPS 端点正常情况下不需要额外网络配置。reading choices 相关报错。这个通常出现在流式响应解析时返回的 JSON 结构里choices字段为空或者格式不符合预期。常见原因是模型名写错了比如你填了一个不存在的 model ID服务端返回了错误结构。检查你的请求体里model字段是否和文档里列出的 Model ID 完全一致。另外如果你用的是 OpenAI SDK确认base_url设置成了https://taotoken.net/api而不是带/v1的完整路径SDK 会自己拼/v1/chat/completions。OAuth 相关报错。如果你在 Claude Code 或类似工具里看到 OAuth 报错通常是因为工具默认走了 OAuth 登录流程而你要用的是 API Key 模式。这时候需要在配置里显式指定使用 API Key把 Base URL、Key、Model ID 三件套填全。以 Claude Code 为例配置文件里需要同时有ANTHROPIC_BASE_URL、ANTHROPIC_AUTH_TOKEN、ANTHROPIC_MODEL三个字段缺一个都可能触发 OAuth 回退。具体字段名以接入文档为准。还有一个数据库侧的高频错ORA-00604 递归 SQL 级别 1 出现错误。这个往往和 ORA-01000 一起出现因为 Oracle 在抛 ORA-01000 之前内部会先执行一些递归 SQL 去记录错误结果递归 SQL 自己也需要游标游标不够就又抛 ORA-00604。所以你看到 ORA-00604 套 ORA-01000 再套 ORA-00604 这种嵌套不要慌根因还是最里层的 ORA-01000。最后提醒一个容易忽略的点连接池的validationQuery也会占游标。如果你配了SELECT 1 FROM DUAL作为心跳检测每次检测都会打开一个游标。正常情况下检测完就关但如果检测逻辑本身有 bug或者检测频率过高比如timeBetweenEvictionRunsMillis设成 1000也会累积游标。建议心跳间隔不要低于 30 秒validationQuery用SELECT 1 FROM DUAL这种最轻量的。6. 把排查流程固化下来从救火到预防ORA-01000 这类问题的特点是「爆发时很吓人根因很简单但排查过程容易走弯路」。我自己的做法是把这次排查的步骤固化成一个 checklist下次再遇到类似问题直接照着走不用重新想。第一步永远是看堆栈里最内层的业务代码行号。像这次报错里的DbCon.java:731直接去那一行看是不是executeQuery调用然后往上找这个方法的finally块或者 try-with-resources 结构。十有八九问题就在那里。第二步是查v$open_cursor用数据说话。不要凭感觉猜「可能是游标数太小」先看当前到底哪个会话占了多少游标是不是你的应用账号。如果是再去看具体是哪条 SQL。第三步是改代码用 try-with-resources 重写所有 JDBC 调用点。这一步可以用 TaoToken 通道让模型帮你批量 review把项目里所有executeQuery、executeUpdate的调用点列出来逐个检查资源关闭。请求示例curl https://taotoken.net/api/v1/chat/completions \ -H Content-Type: application/json \ -H Authorization: Bearer sk-你的Key \ -d { model: gpt-4o, messages: [ {role: user, content: 请列出 Java JDBC 中会导致 Oracle 游标泄漏的 6 种典型写法每种给出错误示例和正确示例用代码块展示} ] }第四步是加监控。在连接池配置里打开removeAbandoned或leakDetectionThreshold让泄漏在发生时就有日志而不是等几天后爆炸。同时在数据库侧可以写一个定时任务每隔 10 分钟查一次v$resource_limit里open_cursors的current_utilization超过阈值就告警。第五步是压测验证。用前面说的循环调用脚本跑 500 次观察游标数是否回落。这一步做完你才能放心地说「修好了」。如果你需要长期做这类数据库排查和 Java 代码审查Coding Plan https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 会比按次调用更划算适合把 AI 助手当成日常排查工具来用。API Key 在控制台 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 随时可以创建和轮换接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 有完整的配置说明。最后说一个我踩过的坑有一次我明明把ResultSet和Statement都关了游标还是涨。查了半天发现是连接池的testOnBorrow配了true每次借连接都跑一次SELECT 1 FROM DUAL而那个版本的驱动在特定情况下没有正确释放这个检测游标。后来把testOnBorrow改成false改用testWhileIdle问题就消失了。所以排查游标泄漏时别忘了把连接池自身的检测行为也算进去。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑