尧图精选

ref cursor 与 sys_refcursor 传递结果集:TaoToken 统一 Key 下的可复现调试清单

🕒 发布时间:2026/10/2 12:30:28 📁 来源:尧图网络
1. 为什么 ref cursor 传结果集总在客户端“断片”ref cursor和sys_refcursor是 Oracle PL/SQL 里把结果集从数据库端“递”给客户端最常用的两种游标变量。它们能做什么简单说就是让存储过程、函数、包把一条查询的“句柄”交出去客户端拿到句柄后再自己取数。适合谁适合写 PL/SQL 接口、做数据同步、给 Java/Python/Go 应用返回多行结果的开发者。但实际调试时问题往往不在“能不能打开游标”而在“结果集传出去之后还完整吗”。我见过太多场景SQL*Plus 里print :v明明 15 行换成应用端就只剩 3 行或者存储过程里fetch ... bulk collect取完emp_tab.count却是 0再或者函数返回sys_refcursorselect f_get_emp from dual能出结果但客户端解析时报ORA-01000: maximum open cursors exceeded。这些都不是语法错而是游标生命周期、绑定方式、取数链路没对齐。这篇就按“可复现调试清单”来写。我会先给一套最小可跑的建表与存储过程脚本再把 TaoToken 统一 Key 的 Base URL 配置片段贴出来让你用同一个 Key 走 API 通道去验证结果集返回是否完整。重点不是教你注册而是教你定位游标没关、结果集截断、强类型不匹配、bulk collect后count为 0 这几类高频坑。先明确一个概念ref cursor是“游标变量”它本身不存数据只存一个指向结果集的指针。sys_refcursor是 Oracle 9i 之后内置的弱类型ref cursor不用自己定义类型直接out sys_refcursor就能用。弱类型意味着它不规定返回的列结构灵活但容易在客户端解析时对不上字段强类型ref cursor return emp%rowtype则要求open ... for的查询列必须严格匹配否则编译期就报PLS-00382: expression is of wrong type。调试的核心动作只有三个打开、绑定、取数。打开时看open ... for的 SQL 是否真的执行绑定时看out参数有没有被正确赋值取数时看fetch循环有没有漏掉exit when ...%notfound以及bulk collect之后有没有误判count。下面从建表开始一步步复现。2. TaoToken 前置统一 Key 与 Base URL 配置片段在验证结果集之前先把 API 通道配好。TaoToken 的作用是给多个模型/通道提供一个统一的 Key 和 Base URL这样你在调试 Oracle 结果集返回时不用为每个客户端单独换配置。官网入口是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 地址是 https://taotoken.net/api 注意 API 地址不加 UTM 参数。你需要准备三件套Base URL、API Key、Model ID。Base URL 填https://taotoken.net/apiKey 在控制台创建Model ID 按你实际要调的模型填。如果你用的是 Claude Code 这类编码工具配置片段可以写成 JSON如果是 Cline MCP 或 Codex 的auth.json路径和字段名要对齐。先给一个通用的 JSON 配置片段路径按你的工具实际位置放{ base_url: https://taotoken.net/api, api_key: sk-你的TaoTokenKey, model: 你的ModelID, timeout: 60 }如果你用的是 Claude Code 的 settings 风格可以写成{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_API_KEY: sk-你的TaoTokenKey, ANTHROPIC_MODEL: 你的ModelID } }注意这里 Base URL 和 Key 必须成对出现Model ID 不能空。很多人只填了 Base URL 和 Key结果请求返回401或者model not found就是因为 Model ID 没对齐。TaoToken 的 API Key 页面在 https://taotoken.net/api-keys 接入文档在 https://taotoken.net/doc 模型对话入口在 https://taotoken.net/chat 长期编码或 Agent 场景可以看 https://taotoken.net/coding-plan 。配置好之后先别急着连 Oracle。用最小请求验证通道是否通发一条最简单的对话请求看返回里有没有choices字段。如果返回local proxy failed或reading choices报错说明 Base URL 或网络层有问题不是 Oracle 游标的问题。这一步能帮你把“API 通道故障”和“结果集截断”分开。3. 可复制配置建表、存储过程与游标脚本这一节给完整可复制的脚本。先建一张最小表模拟emp场景CREATE TABLE emp ( empno NUMBER(4) PRIMARY KEY, ename VARCHAR2(10), job VARCHAR2(9), mgr NUMBER(4), hiredate DATE, sal NUMBER(7,2), comm NUMBER(7,2), deptno NUMBER(2) ); INSERT INTO emp VALUES (7369,SMITH,CLERK,7902,TO_DATE(1980-12-17,YYYY-MM-DD),800,NULL,20); INSERT INTO emp VALUES (7499,ALLEN,SALESMAN,7698,TO_DATE(1981-02-20,YYYY-MM-DD),1600,300,30); INSERT INTO emp VALUES (7521,WARD,SALESMAN,7698,TO_DATE(1981-02-22,YYYY-MM-DD),1250,500,30); INSERT INTO emp VALUES (7566,JONES,MANAGER,7839,TO_DATE(1981-04-02,YYYY-MM-DD),2975,NULL,20); INSERT INTO emp VALUES (7654,MARTIN,SALESMAN,7698,TO_DATE(1981-09-28,YYYY-MM-DD),1250,1400,30); INSERT INTO emp VALUES (7698,BLAKE,MANAGER,7839,TO_DATE(1981-05-01,YYYY-MM-DD),2850,NULL,30); INSERT INTO emp VALUES (7782,CLARK,MANAGER,7839,TO_DATE(1981-06-09,YYYY-MM-DD),2450,NULL,10); INSERT INTO emp VALUES (7788,SCOTT,ANALYST,7566,TO_DATE(1987-04-19,YYYY-MM-DD),3000,NULL,20); INSERT INTO emp VALUES (7839,KING,PRESIDENT,NULL,TO_DATE(1981-11-17,YYYY-MM-DD),5000,NULL,10); INSERT INTO emp VALUES (7844,TURNER,SALESMAN,7698,TO_DATE(1981-09-08,YYYY-MM-DD),1500,0,30); INSERT INTO emp VALUES (7876,ADAMS,CLERK,7788,TO_DATE(1987-05-23,YYYY-MM-DD),1100,NULL,20); INSERT INTO emp VALUES (7900,JAMES,CLERK,7698,TO_DATE(1981-12-03,YYYY-MM-DD),950,NULL,30); INSERT INTO emp VALUES (7902,FORD,ANALYST,7566,TO_DATE(1981-12-03,YYYY-MM-DD),3000,NULL,20); INSERT INTO emp VALUES (7934,MILLER,CLERK,7782,TO_DATE(1982-01-23,YYYY-MM-DD),1300,NULL,10); INSERT INTO emp VALUES (1111,YODA,JEDI,NULL,TO_DATE(1981-11-17,YYYY-MM-DD),5000,NULL,NULL); COMMIT;然后写一个用sys_refcursor作为out参数的存储过程CREATE OR REPLACE PROCEDURE pr_sys_refcursor(v_sys OUT SYS_REFCURSOR) AS BEGIN OPEN v_sys FOR SELECT * FROM emp; END; /再写一个用ref cursor作为包内类型的函数CREATE OR REPLACE PACKAGE pkg_ref_cursor AS TYPE ref_type IS REF CURSOR; FUNCTION f_ref RETURN ref_type; END; / CREATE OR REPLACE PACKAGE BODY pkg_ref_cursor AS FUNCTION f_ref RETURN ref_type IS cursor_ref ref_type; BEGIN OPEN cursor_ref FOR SELECT * FROM emp; RETURN cursor_ref; END; END; /强类型ref cursor的包定义也要有方便后面测PLS-00382CREATE OR REPLACE PACKAGE ref_package AS TYPE emp_record_type IS RECORD( ename VARCHAR2(25), job VARCHAR2(10), sal NUMBER(7,2) ); TYPE weak_ref_cursor IS REF CURSOR; TYPE strong_ref_cursor IS REF CURSOR RETURN emp%ROWTYPE; TYPE strong_ref2_cursor IS REF CURSOR RETURN emp_record_type; END ref_package; /强类型过程CREATE OR REPLACE PROCEDURE test_ref_strong( p_deptno emp.deptno%TYPE, p_cursor OUT ref_package.strong_ref_cursor ) IS BEGIN OPEN p_cursor FOR SELECT * FROM emp WHERE deptno p_deptno; END test_ref_strong; /弱类型过程走不同分支返回不同结果集CREATE OR REPLACE PROCEDURE test_ref_weak( p_deptno emp.deptno%TYPE, p_cursor OUT ref_package.weak_ref_cursor ) IS BEGIN CASE p_deptno WHEN 10 THEN OPEN p_cursor FOR SELECT empno, ename, sal, deptno FROM emp WHERE deptno p_deptno; WHEN 20 THEN OPEN p_cursor FOR SELECT * FROM emp WHERE deptno p_deptno; END CASE; END; /这些脚本就是后面验证和排障的基线。注意open ... for后面跟的是字符串 SQL 时弱类型游标不会在编译期检查列结构强类型会检查。这就是为什么强类型写错列会直接报PLS-00382。4. 验证请求用最小动作确认结果集完整先在 SQL*Plus 里验证sys_refcursor的绑定与取数VARIABLE v REFCURSOR; EXEC pr_sys_refcursor(:v); PRINT :v;如果PRINT :v输出 15 行说明存储过程打开游标没问题。接着在 PL/SQL 块里用bulk collect取数检查countDECLARE TYPE emp_type IS TABLE OF emp%ROWTYPE; emp_tab emp_type; v SYS_REFCURSOR; BEGIN pr_sys_refcursor(v); FETCH v BULK COLLECT INTO emp_tab; DBMS_OUTPUT.PUT_LINE(count || emp_tab.COUNT); FOR i IN 1..emp_tab.COUNT LOOP DBMS_OUTPUT.PUT_LINE(emp_tab(i).ename || , || emp_tab(i).empno); END LOOP; CLOSE v; END; /这里的关键动作是CLOSE v。如果你漏了CLOSE在同一个会话里反复调用就会累积打开游标最终触发ORA-01000: maximum open cursors exceeded。bulk collect之后emp_tab.COUNT应该是 15如果为 0先检查pr_sys_refcursor里的OPEN是否真的执行以及FETCH是否在OPEN之后。再验证函数返回sys_refcursorSELECT pkg_ref_cursor.f_ref FROM dual;SQL*Plus 会显示CURSOR STATEMENT : 1和结果集。如果这里只显示游标语句但没有行通常是客户端取数方式的问题不是函数没返回。换到应用端时要确保客户端驱动支持REF CURSOR类型映射。用 TaoToken 的 API 通道做最小验证时发一条请求检查返回 JSON 里choices是否存在、内容是否完整。如果返回401先查 Key如果返回local proxy failed查 Base URL 和网络如果返回reading choices相关错误查响应体是否被截断。这一步的目的是确认“通道返回完整”再去对比 Oracle 端结果集是否完整。强类型测试VARIABLE c REFCURSOR; EXEC test_ref_strong(10, :c); PRINT c;如果test_ref_strong里的SELECT *列结构和emp%ROWTYPE不一致编译期就会报PLS-00382: expression is of wrong type。这是强类型的保护机制不是 bug。弱类型test_ref_weak传 10 和 20 会返回不同列结构客户端解析时要按实际列处理。管道函数场景也要验证。下面这个包体是修正后的版本去掉了多余的LOOPCREATE OR REPLACE PACKAGE PCKG_CSM_DATA_STL_SET IS TYPE RET_PATH_SET IS TABLE OF NUMBER; TYPE CV_TYPE IS REF CURSOR; FUNCTION F_RET_PATH_SET(I_PRTN_ID NUMBER) RETURN RET_PATH_SET PIPELINED; END; / CREATE OR REPLACE PACKAGE BODY PCKG_CSM_DATA_STL_SET IS FUNCTION F_RET_PATH_SET(I_PRTN_ID NUMBER) RETURN RET_PATH_SET PIPELINED IS CV SYS_REFCURSOR; V_PRTN_ID PCKG_CSM_DATA_STL_SET.RET_PATH_SET; RS NUMBER; BEGIN OPEN CV FOR SELECT empno FROM emp WHERE deptno I_PRTN_ID; FETCH CV BULK COLLECT INTO V_PRTN_ID; RS : V_PRTN_ID.FIRST(); WHILE RS IS NOT NULL LOOP PIPE ROW(V_PRTN_ID(RS)); RS : V_PRTN_ID.NEXT(RS); END LOOP; CLOSE CV; RETURN; END F_RET_PATH_SET; END; /调用SELECT * FROM TABLE(PCKG_CSM_DATA_STL_SET.F_RET_PATH_SET(10));如果这里返回空先看FETCH ... BULK COLLECT后V_PRTN_ID.COUNT是否为 0再检查OPEN CV FOR的WHERE条件是否匹配到数据。管道函数里PIPE ROW之前必须确保集合有元素否则FIRST()返回 NULLWHILE直接跳过。5. 本篇常见错排查401、local proxy failed、reading choices、OAuth排障要按“先通道后数据库”的顺序。下面这些报错都是真实会遇到的对照着查。401 UnauthorizedTaoToken 的 Key 没填、填错、或者 Base URL 和 Key 不匹配。检查https://taotoken.net/api是否写对Key 是否从 https://taotoken.net/api-keys 复制完整。如果用的是 Claude Code 的 settings确认ANTHROPIC_API_KEY字段名没写错。local proxy failed通常是 Base URL 不可达或本地网络层拦截。先确认https://taotoken.net/api能通再检查工具里的代理配置是否把请求转到了错误地址。注意不要用任何非官方通道统一走 TaoToken 的 API 地址。reading choices相关报错响应体里没有choices字段或者响应被截断。先发最小请求看返回结构如果返回的是错误 JSON按错误信息查如果返回正常但客户端解析失败检查客户端是否按流式/非流式正确读取。OAuth相关报错如果你用的是需要 OAuth 的工具确认 OAuth 流程是否走完token 是否过期。TaoToken 的 API Key 方式不需要 OAuth直接用 Key 即可。如果工具强制 OAuth检查是否把 Base URL 配到了 OAuth 端点而不是 API 端点。Oracle 侧报错ORA-01000: maximum open cursors exceeded游标没关。每个OPEN都要有对应的CLOSE尤其是在循环里反复OPEN的场景。用V$OPEN_CURSOR查当前打开的游标。PLS-00382: expression is of wrong type强类型ref cursor的OPEN ... FOR查询列和声明的返回类型不匹配。检查SELECT列数、列类型、列顺序是否和emp%ROWTYPE或自定义 RECORD 一致。ORA-06550/PLS-00201包或过程没编译成功或者权限不足。先SHOW ERRORS看具体行。bulk collect后count0FETCH没执行或者OPEN的 SQL 没返回行。在FETCH后立刻打印SQL%ROWCOUNT和集合COUNT确认取数链路。结果集截断客户端只取了前 N 行。检查FETCH循环的退出条件bulk collect的LIMIT是否设了太小以及客户端驱动的fetchSize设置。SQL*Plus 里SET PAGESIZE和SET LINESIZE也会影响显示但不影响实际取数。三件套再强调一次Base URL 填https://taotoken.net/apiKey 填 TaoToken 控制台创建的 KeyModel ID 填你实际用的模型。这三个任何一个缺失或写错都会在验证请求阶段暴露出来。6. 语义一致 CTA按场景选入口如果你现在卡在排障或接入阶段先去 API Keys 页面拿 Key再看接入文档对齐 Base URL 和 Model IDhttps://taotoken.net/api-keys 和 https://taotoken.net/doc 。如果你要验证模型返回是否完整用模型对话入口发最小请求https://taotoken.net/chat 。如果你是长期做编码或 Agent需要稳定通道看 Coding Planhttps://taotoken.net/coding-plan 。控制台在 https://taotoken.net/console Claude Code 相关配置参考 https://taotoken.net/claudecode-anthropic 。最后给一个实用技巧调试ref cursor时先在 SQL*Plus 里用PRINT :v确认结果集行数再用bulk collect确认count最后才去客户端验证。这样能把“数据库端结果集完整”和“客户端取数完整”分开定位。游标用完立刻CLOSE强类型查询列严格对齐bulk collect后先看COUNT再循环。这三步做完大部分结果集截断和游标泄漏都能当场复现并修掉。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →