news 2026/9/26 3:40:39

java.sql.SQLException: ORA-00604 递归 SQL 报错排查:从 open_cursors 到 TaoToken 配置骨架

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
java.sql.SQLException: ORA-00604 递归 SQL 报错排查:从 open_cursors 到 TaoToken 配置骨架

1. 从一次保存失败说起:ORA-00604 为什么总跟 ORA-01000 一起出现

线上系统保存一条业务数据,接口直接抛java.sql.SQLException: ORA-00604: 递归 SQL 级别 1 出现错误,堆栈往下翻还能看到ORA-01000: 超出打开游标的最大数。很多人第一反应是「游标不够,调大 open_cursors 就完事」,但如果你只改参数不查根因,过几天同样的报错还会回来。

先说清楚这两个错误的关系。ORA-00604 是 Oracle 在递归 SQL(数据库内部为了执行你的语句而自己发起的 SQL)里出错了,它本身是个「外壳错误」,真正的原因藏在它下面那行。ORA-01000 才是内核:当前会话打开的游标数超过了open_cursors限制。递归 SQL 之所以被牵扯进来,是因为很多内部操作(解析、权限校验、触发器、审计)也要占游标,一旦游标池见底,最先崩的就是这些递归调用。

所以排查思路是两层:先定位是谁把游标耗光了,再决定 open_cursors 调到多少、连接池怎么配。这篇面向 Java 后端和 DBA,给一套能直接复制的查询、调整 SQL 和连接池参数,最后附上 TaoToken 统一 Key/API 通道的config.toml配置骨架,方便你把模型调用和数据库排查脚本放在同一套配置体系里管理。

适合谁看:正在被 ORA-00604/ORA-01000 反复折磨的后端同学、需要给应用定游标上限的 DBA、以及想把排查动作固化成脚本的人。

2. 前置准备:确认版本、权限与 TaoToken 通道

动手前先确认三件事,能省掉后面一半的返工。

第一,确认数据库版本和当前会话能查动态性能视图。v$open_cursor、v$session、v$sql这些视图普通业务账号通常没权限,排查阶段建议用有SELECT ANY DICTIONARY或 DBA 角色的账号,生产上临时授权、查完回收。

-- 确认版本,不同版本 v$open_cursor 字段略有差异 SELECT banner FROM v$version; -- 确认当前用户是否有权限查会话与游标视图 SELECT COUNT(*) FROM v$open_cursor;

第二,确认应用侧连接池类型和版本。HikariCP、Druid、Tomcat JDBC 对游标的处理方式不同,后面配置片段会分别给。

第三,如果你打算把排查脚本、模型调用统一走一个 API 通道,可以先把 TaoToken 的 Key 准备好。它的作用是给多个模型/工具提供一个统一的 Key 和 API 入口,省得每个服务各配一套密钥。注册入口在官网 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,登录后在控制台 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 创建 Key,API 基址是 https://taotoken.net/api (这个地址不加 UTM 参数)。Key 生成后先别急着写进代码,第 4 节会给完整的config.toml骨架和连通性验证。

注意:数据库排查脚本里不要硬编码任何密钥,统一从环境变量或配置文件读取,避免 Key 跟着 SQL 脚本一起进版本库。

3. 可复制配置:从 open_cursors 查询到连接池参数

3.1 查当前游标上限和实际占用

先看上限,再看谁在用。这两步顺序不能反,否则你调完参数也不知道有没有生效。

-- 查看当前 open_cursors 上限 SHOW PARAMETER open_cursors; -- 或者用视图查,方便脚本化 SELECT name, value, isdefault FROM v$parameter WHERE name = 'open_cursors';

接着定位游标消耗大户。下面这条按会话统计当前打开的游标数,降序排列,排在前面的就是嫌疑对象。

SELECT s.sid, s.serial#, s.username, s.program, s.machine, COUNT(*) AS cursor_cnt 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.machine ORDER BY cursor_cnt DESC;

如果某个 JDBC 连接对应的会话游标数几百上千,基本可以锁定是应用没关游标(Statement/ResultSet没 close)或者连接池把游标缓存开太大。

3.2 调整 open_cursors

确认是上限太低而不是泄漏后,再调参数。scope=both表示内存和 spfile 同时改,重启也保留。

-- 临时+持久调整,建议按业务峰值留 2~3 倍余量 ALTER SYSTEM SET open_cursors = 1000 SCOPE = BOTH; -- 验证 SHOW PARAMETER open_cursors;

调完不需要重启实例,新会话立即生效,老会话在重连后生效。这里有个坑:open_cursors是每会话上限,不是全局总数。1000 意味着单个会话最多开 1000 个游标,如果你有 200 个连接,理论上限是 20 万,但实际受sessions和 PGA 限制,别盲目往大了调。

3.3 JDBC 连接池参数片段

光调数据库不够,连接池侧的游标缓存和连接数才是泄漏高发区。下面给 HikariCP 和 Druid 两套。

HikariCP:

spring: datasource: hikari: maximum-pool-size: 20 minimum-idle: 5 connection-timeout: 30000 idle-timeout: 600000 max-lifetime: 1800000 # 关键:Oracle 下建议关闭语句缓存或设小,避免游标堆积 >spring: datasource: druid: initial-size: 5 max-active: 20 min-idle: 5 max-wait: 30000 # 关闭游标缓存,排查阶段先关,稳定后再按需开 pool-prepared-statements: false max-open-prepared-statements: 0 filters: stat,wall

oracle.jdbc.implicitStatementCacheSize这个参数是重点。它默认会缓存预编译语句,缓存本身不释放游标,高并发下很容易把open_cursors顶满。排查阶段先设 0,确认稳定后再逐步调大观察。

3.4 TaoToken config.toml 配置骨架

把模型调用和排查脚本统一到一个通道,配置集中管理。下面这份骨架可以直接改 Key 用。

# config.toml - TaoToken 统一 Key/API 通道配置骨架 [default] # API 基址,固定不加 UTM base_url = "https://taotoken.net/api" # Key 从环境变量读取,不要硬编码 api_key = "${TAOTOKEN_API_KEY}" # 请求超时,排查脚本建议设长一点 timeout_seconds = 60 # 失败重试次数 max_retries = 3 [models] # 默认对话模型 chat = "gpt-4o-mini" # 代码/排查脚本生成用 coding = "claude-3-5-sonnet" [logging] level = "info" # 排查阶段打开请求日志,方便定位 log_requests = true

Key 在控制台创建:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。环境变量设置:

export TAOTOKEN_API_KEY="你的Key"

4. 验证请求:确认游标生效与通道连通

4.1 验证 open_cursors 是否生效

改完参数后,新开一个会话查一次,再跑一段会开游标的 SQL 观察计数。

-- 新会话确认上限 SELECT value FROM v$parameter WHERE name = 'open_cursors'; -- 观察当前会话游标数变化 SELECT COUNT(*) FROM v$open_cursor WHERE sid = SYS_CONTEXT('USERENV','SID');

如果上限显示 1000,且业务高峰时单会话游标数稳定在 200 以内,说明调整到位。如果还是往上涨,回到 3.1 的会话统计继续找泄漏点。

4.2 验证 TaoToken 通道连通

用 curl 发一个最小请求,确认 Key 和基址都对。

curl -s -X POST "https://taotoken.net/api/v1/chat/completions" \ -H "Authorization: Bearer ${TAOTOKEN_API_KEY}" \ -H "Content-Type: application/json" \ -d '{ "model": "gpt-4o-mini", "messages": [{"role": "user", "content": "ping"}], "max_tokens": 10 }'

返回里带choices字段就说明通道正常。如果返回 401,检查 Key 是否复制完整;返回 404,检查base_url有没有多写或少写/v1。想直接在网页里试模型,可以用模型对话入口:https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。

4.3 把排查脚本接进通道

一个实用做法:把 3.1 的游标统计 SQL 结果丢给模型,让它帮你判断哪个会话异常。脚本里读config.toml的base_url和api_key,请求走同一个通道,不用再单独配密钥。

import os, tomllib, requests with open("config.toml", "rb") as f: cfg = tomllib.load(f) base = cfg["default"]["base_url"] key = os.environ["TAOTOKEN_API_KEY"] resp = requests.post( f"{base}/v1/chat/completions", headers={"Authorization": f"Bearer {key}"}, json={ "model": cfg["models"]["coding"], "messages": [{"role": "user", "content": "分析这段游标统计:..."}] }, timeout=cfg["default"]["timeout_seconds"], ) print(resp.json()["choices"][0]["message"]["content"])

5. 本篇常见错排查

改了 open_cursors 但报错依旧:先确认改的是不是当前实例。RAC 环境下ALTER SYSTEM默认只影响当前节点,需要SID='*'或逐节点执行。另外老连接不会自动继承新参数,重启连接池或等连接自然淘汰。

ORA-01000 消失了但 ORA-00604 还在:说明递归 SQL 的触发源不是游标,可能是触发器里的 DDL、审计策略或权限问题。查v$sql里最近执行的递归语句,或者开 10046 trace 抓递归调用链。

连接池调小后吞吐下降:maximum-pool-size不是越大越好。Oracle 每连接有固定内存开销,连接数过多反而拖慢。先按CPU 核数 * 2 + 磁盘数估算,再压测微调。

Druid 的pool-prepared-statements关了性能变差:这是排查期的取舍。稳定后可以重新打开,但把max-open-prepared-statements设成单连接游标预算的 1/3 以内,别让它无限缓存。

TaoToken 请求偶发超时:检查timeout_seconds是否太短,长文本排查建议 60 秒以上;同时确认max_retries生效,网络抖动时能自动重试。

Key 泄漏风险:永远不要把 Key 写进config.toml提交到仓库,用${TAOTOKEN_API_KEY}占位,CI 里通过 secrets 注入。

6. 固化配置:把游标上限和 API 通道一起管起来

排查完别急着收工,把这次的动作固化成可复用的东西。数据库侧,把open_cursors的目标值写进初始化脚本,新环境部署时自动带上;应用侧,把连接池的游标缓存参数写进配置模板,避免下次有人手滑打开。

如果你还在做长期编码或 Agent 类项目,需要频繁调用模型,可以看下 Coding Plan:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,它把常用编码模型的调用打包成一个通道,配合上面的config.toml骨架直接能用。接入细节和参数说明在文档里:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。

最后留一个我踩过的坑:open_cursors调到 1000 后别就不管了,加个监控,单会话游标数超过阈值就告警。游标泄漏往往是代码里某个ResultSet忘了关,参数只是给你争取排查时间,真正止血还得靠代码 review 和连接池配置双管齐下。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/26 3:39:04

MES 系统中的手动排产与自动排产:区别、场景与落地建议

一、引言在制造执行系统(MES)中,排产是把生产订单、设备产能、物料、人员和工艺路线等信息转化为具体生产计划的过程。MES 通常同时提供手动排产和自动排产两种能力,很多工厂在实施过程中最大的困惑不是“选哪一种”,而…

作者头像 李华
网站建设 2026/9/26 3:38:45

告别命令行!用 TaoToken 可视化配置 OpenClaw,Windows 新手轻松上手

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/26 3:37:55

全链路智能科技:呼吸健康生态从感知到干预的闭环实践

1. 项目溯源:从单品智能到生态智能,呼吸健康赛道为何需要一次范式转变先聊一个让我印象挺深的现象。前几年做环境监测类产品,市面上能见到的方案大多是"空气数据采集器":一台设备放在客厅,屏幕上跳动着PM2.5…

作者头像 李华
网站建设 2026/9/26 3:37:55

Hot100 代码随想录:最长回文子串、合并有序数组与合并链表

Java刷题笔记(0923):最长回文子串、合并有序数组与合并链表 学习日期:09 月 23 日 关键词:中心扩展、双指针、虚拟头节点、排序算法 本篇包含三道题,分别练习字符串中心扩展、数组尾部双指针和链表虚拟头节…

作者头像 李华