
1. Oracle 游标到底解决什么问题为什么你写的 PL/SQL 总在取数上翻车如果你写过 Oracle 存储过程大概率遇到过这种场景需要按行处理一批数据每行都要做判断、计算、再更新回去。用一条UPDATE ... WHERE搞不定因为每行的计算规则不一样用集合操作又太绕。这时候游标Cursor就是最直接的工具。游标本质上是一个指向查询结果集的指针。你可以把它理解成 Java 里的ResultSet查询语句执行后结果集并不会一次性全塞进变量而是通过游标一行一行地取。Oracle 里游标分两大类——显式游标和隐式游标再往深了还有REF CURSOR游标变量和游标FOR循环。很多人写 PL/SQL 出问题不是语法不会而是选错了游标类型或者在OPEN/FETCH/CLOSE的生命周期上踩了坑。这篇内容面向的是已经在写 Oracle PL/SQL、但对游标选型还模棱两可的开发者。我会把显式游标、隐式游标、REF CURSOR、游标FOR循环四种写法摆在一起对比给出可以直接复制运行的代码片段并用DBMS_OUTPUT验证逐行取数的结果。同时因为现在很多团队在做多模型调用、需要统一管理鉴权配置我也会结合 TaoToken 的统一 Key/API 通道演示怎么把数据库侧的开发配置和外部 API 鉴权配置放在一套体系里管理避免 Key 散落在各个脚本里。先说结论日常业务处理优先用游标FOR循环它自动处理OPEN/FETCH/CLOSE代码最短、最不容易出错需要把结果集返回给外部程序比如 Java、Python 调用存储过程时用REF CURSOR只有需要精细控制取数节奏、手动判断%ISOPEN、%ROWCOUNT时才用显式游标手写循环。隐式游标则是 Oracle 自己为每条 DML 语句悄悄开的你只能读它的属性不能手动控制。下面从最基础的显式游标开始一步步把四种写法跑通。2. TaoToken 统一 Key 前置准备把鉴权配置从脚本里抽出来在进入游标代码之前先把外部 API 的鉴权配置理清楚。很多团队的做法是每个脚本里硬编码一个 Key或者每个开发者本地存一份.env。时间一长Key 过期了不知道谁在用换模型了要改十几个文件。TaoToken 的思路是提供一个统一的 API 通道你只需要维护一份 Key就能在多个模型调用场景里复用。你需要先拿到自己的 API Key。访问控制台创建https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite创建完成后在 API Keys 页面可以看到完整的 Key 字符串https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteTaoToken 的 API 入口是https://taotoken.net/api注意这个地址不带任何查询参数是纯粹的 API Base URL。所有模型调用都走这个入口具体调哪个模型由请求体里的model字段决定。这一点和游标的设计思路很像游标是「一个指针指向不同结果集」TaoToken 是「一个 Base URL指向不同模型」。配置的时候我建议把 Base URL 和 Key 放在环境变量里而不是写死在代码中。比如在 Linux/macOS 的~/.bashrc或~/.zshrc里export TAOTOKEN_BASE_URLhttps://taotoken.net/api export TAOTOKEN_API_KEYsk-你的实际KeyWindows PowerShell 里则是$env:TAOTOKEN_BASE_URL https://taotoken.net/api $env:TAOTOKEN_API_KEY sk-你的实际Key如果你用的是 Claude Code 这类编码工具配置方式略有不同。Claude Code 需要设置ANTHROPIC_BASE_URL和ANTHROPIC_API_KEY两个环境变量Base URL 同样指向 TaoToken 的 API 入口。具体接入文档在这里https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite为什么要先讲这个因为游标处理的是数据库内部的数据流而多模型调用处理的是外部服务的数据流。两者都需要一个「统一的入口 统一的凭证」。你在 Oracle 里用REF CURSOR把结果集交给外部程序外部程序拿着这个结果集去调用模型 API如果 API 鉴权配置散乱整个链路就会在最后一环断掉。把 Key 管理前置后面调试游标时就不会被 401 打断思路。3. 可复制配置四种游标写法与 TaoToken settings 片段这一节直接给代码。我会用 Oracle 经典的emp表作为数据源四种游标写法逐一展开每种都配上DBMS_OUTPUT输出你可以直接在 SQL*Plus 或 SQL Developer 里跑。先确保DBMS_OUTPUT是打开的SET SERVEROUTPUT ON;3.1 显式游标手动 OPEN / FETCH / CLOSE显式游标是最「原始」的写法生命周期完全由你控制。声明、打开、取数、关闭四步一步不能少。DECLARE CURSOR mycur IS SELECT * FROM emp; empInfo emp%ROWTYPE; BEGIN IF mycur%ISOPEN THEN NULL; ELSE OPEN mycur; END IF; LOOP FETCH mycur INTO empInfo; EXIT WHEN mycur%NOTFOUND; DBMS_OUTPUT.put_line(mycur%ROWCOUNT || 雇员编号 || empInfo.empno); DBMS_OUTPUT.put_line(mycur%ROWCOUNT || 雇员姓名 || empInfo.ename); END LOOP; CLOSE mycur; END; /这里有几个关键点。emp%ROWTYPE表示声明一个能装下emp表一整行的变量字段类型自动对齐不用你手写empno NUMBER、ename VARCHAR2(10)。%ISOPEN判断游标是否已经打开避免重复OPEN报ORA-06511: PL/SQL: cursor already open。%NOTFOUND在FETCH之后判断如果没取到数据就退出循环。%ROWCOUNT记录当前已经取了多少行注意它在FETCH之后才更新。CLOSE一定要写。显式游标不关会话里游标数会持续累积超过open_cursors参数限制就会报ORA-01000: maximum open cursors exceeded。3.2 游标 FOR 循环最省心的写法同样的需求用游标FOR循环写出来短一半DECLARE CURSOR mycur IS SELECT * FROM emp; BEGIN FOR empInfo IN mycur LOOP DBMS_OUTPUT.put_line(mycur%ROWCOUNT || 雇员编号 || empInfo.empno); DBMS_OUTPUT.put_line(mycur%ROWCOUNT || 雇员姓名 || empInfo.ename); END LOOP; END; /FOR empInfo IN mycur LOOP这一行Oracle 自动帮你做了三件事打开游标、每次循环取一行、循环结束后关闭游标。empInfo不需要你提前声明循环变量隐式定义类型就是游标结果集的行类型。%ROWCOUNT依然可用。这种写法适合绝大多数业务场景。你不需要关心OPEN和CLOSE也不会忘记EXIT WHEN。唯一需要注意的是循环体内不要对游标本身做FETCH否则会打乱自动取数的节奏。3.3 隐式游标DML 语句的「影子游标」隐式游标不是你声明的是 Oracle 为每条 DMLINSERT、UPDATE、DELETE和单行SELECT INTO自动开的。你只能读它的属性BEGIN UPDATE emp SET sal sal * 1.1 WHERE deptno 10; DBMS_OUTPUT.put_line(受影响行数 || SQL%ROWCOUNT); IF SQL%FOUND THEN DBMS_OUTPUT.put_line(有数据被更新); END IF; IF SQL%NOTFOUND THEN DBMS_OUTPUT.put_line(没有匹配的数据); END IF; END; /SQL%ROWCOUNT、SQL%FOUND、SQL%NOTFOUND、SQL%ISOPEN是隐式游标的四个属性。注意SQL%ISOPEN对隐式游标永远是FALSE因为 Oracle 在执行完 DML 后立即关闭了它。隐式游标不能手动OPEN或FETCH它只服务于「这条语句影响了几行」这个判断。3.4 REF CURSOR把结果集交给外部程序REF CURSOR是游标变量它不绑定具体的查询可以在运行时指向不同的结果集。典型用法是在存储过程里打开一个REF CURSOR返回给调用方CREATE OR REPLACE PROCEDURE get_emp_by_dept( p_deptno IN NUMBER, p_cursor OUT SYS_REFCURSOR ) AS BEGIN OPEN p_cursor FOR SELECT empno, ename, sal FROM emp WHERE deptno p_deptno; END; /调用方比如 Java 的 JDBC、Python 的 cx_Oracle拿到这个SYS_REFCURSOR后像遍历ResultSet一样逐行读取。SYS_REFCURSOR是 Oracle 内置的弱类型游标变量不需要你自定义类型。如果你要自定义强类型DECLARE TYPE emp_cur_type IS REF CURSOR RETURN emp%ROWTYPE; v_cur emp_cur_type; v_emp emp%ROWTYPE; BEGIN OPEN v_cur FOR SELECT * FROM emp WHERE deptno 10; LOOP FETCH v_cur INTO v_emp; EXIT WHEN v_cur%NOTFOUND; DBMS_OUTPUT.put_line(v_emp.empno || - || v_emp.ename); END LOOP; CLOSE v_cur; END; /强类型REF CURSOR要求RETURN子句指定行类型灵活性差一些但编译期能检查更多错误。日常返回给外部程序用SYS_REFCURSOR就够了。3.5 TaoToken settings 配置片段如果你在用 Claude Code 做 PL/SQL 开发辅助配置文件通常放在~/.claude/settings.json。把 TaoToken 的接入信息写进去{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_API_KEY: sk-你的实际Key, ANTHROPIC_MODEL: claude-sonnet-4-20250514 } }如果你用的是 Cline 这类 VS Code 插件配置在插件的 settings 里同样是三件套Base URL 填https://taotoken.net/apiAPI Key 填你的 KeyModel ID 填具体模型名。Codex 的auth.json则是{ base_url: https://taotoken.net/api, api_key: sk-你的实际Key, model: gpt-4o }三件套缺一不可。Base URL 决定请求发到哪里API Key 决定鉴权是否通过Model ID 决定实际调用哪个模型。这和游标的三要素——声明、打开、取数——是对应的少任何一环链路都跑不通。4. 验证请求用 DBMS_OUTPUT 逐行确认游标取数结果代码写完不算完得看到实际输出。这一节把上面的游标跑一遍确认每一行都取到了。先准备测试数据。如果emp表是空的插入几条INSERT INTO emp (empno, ename, deptno, sal) VALUES (7369, SMITH, 20, 800); INSERT INTO emp (empno, ename, deptno, sal) VALUES (7499, ALLEN, 30, 1600); INSERT INTO emp (empno, ename, deptno, sal) VALUES (7521, WARD, 30, 1250); INSERT INTO emp (empno, ename, deptno, sal) VALUES (7566, JONES, 20, 2975); INSERT INTO emp (empno, ename, deptno, sal) VALUES (7782, CLARK, 10, 2450); COMMIT;然后跑游标FOR循环SET SERVEROUTPUT ON; DECLARE CURSOR mycur IS SELECT * FROM emp ORDER BY empno; BEGIN FOR empInfo IN mycur LOOP DBMS_OUTPUT.put_line( 第 || mycur%ROWCOUNT || 行 - 编号: || empInfo.empno || 姓名: || empInfo.ename || 部门: || empInfo.deptno || 工资: || empInfo.sal ); END LOOP; END; /预期输出第1行 - 编号:7369 姓名:SMITH 部门:20 工资:800 第2行 - 编号:7499 姓名:ALLEN 部门:30 工资:1600 第3行 - 编号:7521 姓名:WARD 部门:30 工资:1250 第4行 - 编号:7566 姓名:JONES 部门:20 工资:2975 第5行 - 编号:7782 姓名:CLARK 部门:10 工资:2450如果你在 SQL Developer 里跑记得点开「DBMS Output」面板否则看不到输出。SQL*Plus 里SET SERVEROUTPUT ON之后直接就能看到。再验证一个带条件更新的场景就是开头提到的按部门涨工资DECLARE CURSOR mycur IS SELECT * FROM emp FOR UPDATE; v_new_sal emp.sal%TYPE; BEGIN FOR empInfo IN mycur LOOP IF empInfo.deptno 10 THEN v_new_sal : empInfo.sal * 1.1; ELSIF empInfo.deptno 20 THEN v_new_sal : empInfo.sal * 1.2; ELSIF empInfo.deptno 30 THEN v_new_sal : empInfo.sal * 1.3; ELSE v_new_sal : empInfo.sal; END IF; IF v_new_sal 5000 THEN v_new_sal : 5000; END IF; UPDATE emp SET sal v_new_sal WHERE empno empInfo.empno; DBMS_OUTPUT.put_line( 更新 || empInfo.ename || || empInfo.sal || - || v_new_sal ); END LOOP; COMMIT; END; /注意FOR UPDATE子句它在游标打开时锁定结果集对应的行防止其他会话在你处理过程中修改数据。但这也意味着锁会一直持有到COMMIT或ROLLBACK所以循环体里不要做耗时操作否则会阻塞其他会话。跑完之后查一下结果SELECT empno, ename, deptno, sal FROM emp ORDER BY empno;SMITH 在 20 部门800 × 1.2 960ALLEN 在 30 部门1600 × 1.3 2080WARD 在 30 部门1250 × 1.3 1625JONES 在 20 部门2975 × 1.2 3570CLARK 在 10 部门2450 × 1.1 2695。都没有超过 5000所以按计算结果更新。如果你要验证 TaoToken 的 API 通道是否配通可以用 curl 发一个最小请求curl -X POST $TAOTOKEN_BASE_URL/v1/messages \ -H Content-Type: application/json \ -H x-api-key: $TAOTOKEN_API_KEY \ -H anthropic-version: 2023-06-01 \ -d { model: claude-sonnet-4-20250514, max_tokens: 100, messages: [{role: user, content: 回复 OK 两个字母}] }返回里能看到content字段就说明鉴权通过、通道正常。这一步和游标的DBMS_OUTPUT验证是同一个逻辑先确认最小链路能通再往上叠业务逻辑。5. 本篇常见错排查ORA-01000、401、reading choices 与 OAuth 报错游标和 API 调用都会在细节上翻车。这一节把最常见的几类报错摆出来对照排查。ORA-01000: maximum open cursors exceeded这是显式游标忘记CLOSE的典型后果。每个会话能打开的游标数由open_cursors参数控制默认通常是 300。如果你的循环里反复OPEN不CLOSE很快就会撞上限。排查方法SHOW PARAMETER open_cursors; SELECT sid, count(*) AS cursor_count FROM v$open_cursor GROUP BY sid ORDER BY cursor_count DESC;修复方式有两个一是改用游标FOR循环自动关闭二是显式游标确保每个OPEN都有对应的CLOSE最好放在异常处理的FINALLY逻辑里BEGIN OPEN mycur; LOOP FETCH mycur INTO empInfo; EXIT WHEN mycur%NOTFOUND; -- 处理逻辑 END LOOP; CLOSE mycur; EXCEPTION WHEN OTHERS THEN IF mycur%ISOPEN THEN CLOSE mycur; END IF; RAISE; END;ORA-06511: PL/SQL: cursor already open重复OPEN同一个游标。用%ISOPEN判断可以避免IF NOT mycur%ISOPEN THEN OPEN mycur; END IF;401 UnauthorizedTaoToken API 调用Key 不对、没传、或者传错了 header。检查三件事环境变量TAOTOKEN_API_KEY是否真的导出成功echo $TAOTOKEN_API_KEY看一下请求头里用的是x-api-key还是Authorization: Bearer不同接口要求不同Key 是否已经过期或被删除。去控制台确认https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewritelocal proxy failed这个报错通常出现在本地开发工具比如 Claude Code、Cline配置了代理但代理没起来或者 Base URL 填成了localhost但本地没有服务。检查ANTHROPIC_BASE_URL是否误填成了本地地址。正确值应该是https://taotoken.net/api。如果你本地确实跑了转发服务确认端口和进程状态。reading choices 报错这类报错一般出现在解析模型返回的 JSON 时choices字段读不到。原因可能是请求体格式不对模型没返回预期结构或者返回的是错误信息比如 401、429但代码直接去读choices了。排查时先把原始响应打印出来import os, requests resp requests.post( f{os.environ[TAOTOKEN_BASE_URL]}/v1/chat/completions, headers{Authorization: fBearer {os.environ[TAOTOKEN_API_KEY]}}, json{model: gpt-4o, messages: [{role: user, content: hi}]} ) print(resp.status_code) print(resp.text)先看status_code和原始文本再决定是改鉴权还是改解析逻辑。OAuth 相关报错如果你用的是需要 OAuth 流程的工具报错通常和 token 刷新有关。检查 refresh token 是否过期、回调地址是否和注册时一致。TaoToken 的 API Key 方式是静态 Key不涉及 OAuth 刷新配置更简单。如果你在工具里同时配了 OAuth 和 API Key确认工具实际走的是哪条路径。游标取数结果为空但表里有数据检查WHERE条件、COMMIT是否执行、以及当前会话是否有权限看到那些行。另外%NOTFOUND的判断位置很关键必须在FETCH之后判断如果在FETCH之前判断第一次循环就会退出。6. 语义一致 CTA把游标调试和 API 鉴权收在同一条链路里游标调试通了API 通道也验证过了接下来就是把两者串起来。如果你在做的是长期编码、Agent 类项目需要频繁调用模型来辅助生成 PL/SQL 或做代码审查建议直接上 Coding Plan省去每次单独配 Key 的麻烦https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite如果你只是想快速验证某个模型对游标代码的理解能力用模型对话页面直接试https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite接入文档里有各语言 SDK 的完整示例包括 Python、Node.js、Java 的调用方式https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite回到游标本身最后给一个实用建议在写任何显式游标之前先问自己「能不能用游标 FOR 循环」。90% 的场景答案是可以。只有当你需要把结果集返回给外部程序时才用REF CURSOR只有当你需要手动控制FETCH节奏、或者在循环中间做%ROWCOUNT判断时才手写OPEN/FETCH/CLOSE。选对了游标类型后面 80% 的报错根本不会出现。