从一次线上 ORA-01000 报错说起
凌晨两点,监控群里弹出一条告警:某个跑批任务连续失败,日志里赫然写着java.sql.SQLException: ORA-01000: maximum open cursors exceeded。这个报错对写过 Oracle 的 Java 开发者来说并不陌生,它意味着当前会话打开的游标数量已经超过了数据库参数open_cursors设定的上限。更麻烦的是,它往往不是一次性故障,而是随着循环次数累积、慢慢把游标耗光,等到报错时业务已经跑了一半。
这次我没有直接翻代码,而是把报错栈和可疑的循环片段整理好,交给 Codex 来辅助定位。为了让 Codex 的对话请求稳定跑通,我提前在 TaoToken 官网 创建了 API Key,并把 Codex 的 Base URL 指向 TaoToken 的接口地址。下面把整个排查和配置过程完整记录下来,包括 Codex 的接入方式、如何让它精准找出未关闭的 ResultSet,以及 try-with-resources 的改法。
场景还原:循环里的 Statement 为什么把游标吃光了
先看一段典型的“肇事代码”,结构上很常见:
public void processOrders(Connection conn) throws SQLException { List<String> ids = queryPendingIds(conn); for (String id : ids) { PreparedStatement ps = conn.prepareStatement( "SELECT * FROM order_detail WHERE order_id = ?"); ps.setString(1, id); ResultSet rs = ps.executeQuery(); while (rs.next()) { // 处理业务数据 } // 这里既没有 rs.close(),也没有 ps.close() } }问题的核心在于:在 Oracle 中,每一次createStatement()或prepareStatement()都会在数据库端打开一个游标,executeQuery()返回的ResultSet同样占用游标资源。上面这段代码在循环里反复创建PreparedStatement和ResultSet,却从未关闭。循环几百上千次之后,单个会话的游标数就会触顶,于是抛出 ORA-01000。
很多人会疑惑:同样的代码连 MySQL 为什么没事?因为 MySQL 的游标管理和 Oracle 差异很大,JDBC 驱动对资源的回收策略也不同,所以这类“忘记关 ResultSet”的写法在 MySQL 上可能长期不暴露,一到 Oracle 就原形毕露。这也是为什么这个报错在从 MySQL 迁移到 Oracle 的项目里格外高频。
除了循环内创建,还有几种常见诱因:
- 在循环里调用返回
ResultSet的方法,但调用方没有关闭; - 把
Connection、Statement缓存在成员变量里长期复用,游标只增不减; - 异常路径提前 return 或抛异常,跳过了
close(); - 连接池配置的
open_cursors偏小,正常业务量下也容易触顶。
定位这类问题的关键,是找出“谁打开了游标却没关”。人工翻代码容易漏,尤其是嵌套调用和方法抽取之后。这正是让 Codex 介入的好时机——把报错和代码片段交给它,让它顺着调用链把未关闭的资源点标出来。
TaoToken 前置:给 Codex 配一条稳定的请求通道
在让 Codex 分析代码之前,需要先保证它的对话请求能正常发出。Codex 支持自定义 Base URL,我们可以把它接到 TaoToken 的接口上。整个准备过程只有两步:
- 打开 TaoToken 官网,注册后在控制台创建一个 API Key,形如
YOUR_API_KEY; - 记住接口地址:
https://taotoken.net/api,注意这里不带/v1,填错会导致 404。
如果你用的是 Codex CLI,也可以直接用命令行方式接入,省去手改配置:
npm i -g @taotoken/taotoken taotoken cc -k YOUR_API_KEY -u https://taotoken.net/api -m MODEL_ID这条命令会把 Key、Base URL 和模型 ID 一次性写进 Codex 的配置。对于习惯图形界面的同学,也可以在 Codex 的配置界面里手动填写。无论哪种方式,核心就是三要素:Key、Base URL、模型 ID。
需要提醒的是,TaoToken 在这里扮演的是请求通道的角色,它不替代你的编辑器,也不替你写业务代码。它的价值在于让 Codex 的对话请求有一个稳定、可管理的出口,方便你在排障时反复调用。
可复制配置:Codex 的 config.toml 与 settings.json
Codex 的配置分两种形态,取决于你用的是 CLI 还是 IDE 插件。下面给出可直接复制的模板。
Codex CLI 的config.toml(通常位于~/.codex/config.toml):
model = "MODEL_ID" model_provider = "taotoken" [model_providers.taotoken] name = "TaoToken" base_url = "https://taotoken.net/api" env_key = "TAOTOKEN_API_KEY"然后在环境变量里设置 Key:
export TAOTOKEN_API_KEY=YOUR_API_KEYClaude Code 风格的settings.json(如果你在同类工具里配置,字段名对应ANTHROPIC_*):
{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_API_KEY": "YOUR_API_KEY", "ANTHROPIC_MODEL": "MODEL_ID" } }配置完成后,建议先做一次最小验证,确认通道是通的,再去跑代码分析。验证方式很简单,在 Codex 里发一句“你好,请回复当前模型名称”,如果能正常返回,说明 Key 和 Base URL 都生效了。如果返回 401,多半是 Key 写错;返回 404,多半是 Base URL 多带了/v1。
验证请求:让 Codex 定位未关闭的 ResultSet
通道打通后,就可以把问题交给 Codex 了。这里的关键是把上下文给足:报错原文、数据库类型、可疑代码片段、以及你怀疑的方向。我实际使用的提示词大致如下:
我在 Oracle 上遇到 ORA-01000: maximum open cursors exceeded。 下面是报错栈和一段循环里创建 PreparedStatement 的代码。 请帮我: 1. 找出所有可能未关闭的 ResultSet 和 Statement; 2. 指出在异常路径下哪些 close 会被跳过; 3. 给出 try-with-resources 的改写版本。Codex 返回的分析通常会覆盖几个点:循环内prepareStatement和executeQuery各自占用游标;rs和ps都没有关闭;如果while循环体里抛异常,后面的close根本不会执行。它给出的改写版本一般是这样:
public void processOrders(Connection conn) throws SQLException { List<String> ids = queryPendingIds(conn); String sql = "SELECT * FROM order_detail WHERE order_id = ?"; for (String id : ids) { try (PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, id); try (ResultSet rs = ps.executeQuery()) { while (rs.next()) { // 处理业务数据 } } } } }ResultSet和PreparedStatement都实现了AutoCloseable,用 try-with-resources 后,无论正常结束还是抛异常,资源都会在作用域结束时自动关闭。这样循环再多次,游标也会及时释放,不会累积到触顶。
如果 Codex 指出某个方法返回了ResultSet而调用方没关,那就要进一步调整方法签名,或者把结果在方法内部消费完再返回。这类跨方法的资源泄漏,正是人工排查最容易漏掉的地方。
本篇常见错排查
在配置和使用过程中,有几个错误反复出现,集中列一下:
Base URL 多写/v1:TaoToken 的接口地址是https://taotoken.net/api,不带/v1。写成https://taotoken.net/api/v1会返回 404,这是最高频的配置错误。
Key 未生效:环境变量名要和config.toml里的env_key一致。如果 CLI 读的是TAOTOKEN_API_KEY,你却 export 成了别的名字,就会报 401。
模型 ID 写错:MODEL_ID要和 TaoToken 控制台里可用的模型对应,随便填一个不存在的 ID 会直接报错。
只改了配置没重启:Codex CLI 或 IDE 插件修改配置后需要重启会话,否则仍走旧配置。
把 ORA-01000 当成数据库参数问题:调大open_cursors只能延缓报错,不能根治。真正的修复一定在代码里关闭资源。
try-with-resources 里嵌套顺序写反:ResultSet要在PreparedStatement内层声明,先关结果集再关语句,顺序反了虽然多数驱动能容忍,但不规范。
异常被吞掉导致 close 跳过:即使加了 try-with-resources,如果外层还有catch把异常吞了,也要确认资源作用域是否正确覆盖。
语义一致:把通道和排障串成一条线
回到这次排障本身,ORA-01000 的根因几乎总是代码里未关闭的游标,而定位它的效率取决于你能不能快速把报错和代码上下文交给一个靠谱的分析工具。Codex 负责分析,TaoToken 负责让 Codex 的请求稳定跑通,两者配合,既解决了游标溢出,也顺手验证了通道可用。
如果你也在处理类似的 Oracle 游标问题,可以按下面的路径继续:
- 需要创建 Key、查看接入方式:前往 API Keys 管理 和 接入文档;
- 想先验证模型对话是否正常:打开 模型对话 发一条测试消息;
- 长期用 Codex 做编码和 Agent 任务:了解 Coding Plan,把日常排障和开发都接到同一条通道上。
把游标关好,把通道配好,剩下的就是让工具替你省下翻代码的时间。