尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

Oracle游标(Cursor)实战:从显式游标到游标变量的配置与验证

发布时间:2026/9/25 17:59:46

资讯中心
01
ARTICLE

Oracle游标(Cursor)实战:从显式游标到游标变量的配置与验证

Oracle游标(Cursor)实战:从显式游标到游标变量的配置与验证
1. 为什么你写的游标总是报 ORA-01001刚接触 Oracle PL/SQL 那会儿我最怕看到的就是ORA-01001: invalid cursor。明明代码逻辑看着没问题一跑就报错排查半天发现是游标没打开就 FETCH或者循环里提前 CLOSE 了。游标Cursor这东西说简单也简单——它就是一个指向查询结果集的指针让你能一行一行地处理数据说坑也多隐式游标、显式游标、REF CURSOR 三种类型各有各的脾气属性用错、关闭时机不对、异常处理漏了 CLOSE都会让你在 SQL*Plus 里对着报错发呆。这篇内容面向的是正在做 Oracle 数据库开发或迁移的朋友尤其是从其他数据库转过来、对 PL/SQL 游标写法还不熟的人。我会把显式游标、隐式游标、游标 FOR 循环、游标变量REF CURSOR这四种核心用法拆开讲每种都给可复制的代码再配上在 SQL*Plus 和 SQL Developer 里的验证步骤。最后整理一份常见报错排查清单你遇到ORA-06511、ORA-01001、ORA-06504这些错误时可以直接对照。另外提一句如果你在本地环境调试游标时遇到连接配置、字符集或者客户端工具的问题可以借助 TaoToken 的统一 Key/API 通道来管理你的模型对话和编码辅助请求地址是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 后面我会在接入配置那节具体说怎么用。2. 游标到底是什么从内存工作区到四种类型游标本质上是 SQL 的一个内存工作区系统或用户以变量形式定义它用来临时存储从数据库提取的数据块。打个比方你从磁盘上的表里把数据调到内存里处理就像把仓库里的货搬到工作台上分拣处理完再决定是展示还是写回。没有游标的话你只能一次性把整张表拉出来内存扛不住效率也低。Oracle 里的游标分四种常见形态隐式游标是系统自动帮你开的你写SELECT ... INTO、UPDATE、INSERT、DELETE时Oracle 在背后默默创建了一个游标你不需要声明也不需要打开关闭但可以通过SQL%ROWCOUNT、SQL%FOUND、SQL%NOTFOUND、SQL%ISOPEN这几个属性来了解操作状态。显式游标需要你自己声明、打开、提取、关闭适合处理多行数据。它的属性是%ROWCOUNT、%FOUND、%NOTFOUND、%ISOPEN注意这里没有SQL%前缀直接挂在游标名后面。游标 FOR 循环是显式游标的一种简化写法你不需要手动 OPEN、FETCH、CLOSEOracle 自动帮你完成代码更干净出错概率也低。REF CURSOR 是动态游标它和前面三种最大的区别是结果集在运行期间才决定可以传参数、可以动态拼 SQL适合需要灵活查询的场景。下面这张表帮你快速对照四种类型的差异类型是否需声明是否需手动开关结果集确定时机典型场景隐式游标否否编译期单行 DML、SELECT INTO显式游标是是编译期多行查询逐行处理游标 FOR 循环是或直接子查询否编译期简化遍历逻辑REF CURSOR是是运行期动态 SQL、参数化查询理解了这个分类后面写代码时你就能根据场景快速选型而不是所有地方都硬套一种写法。3. TaoToken 前置统一 Key 与 API 通道的接入配置在开始写游标代码之前先花两分钟把调试环境里的辅助通道配好。TaoToken 提供统一的 Key 和 API 入口方便你在做数据库开发时调用模型对话、代码补全或者文档查询能力。官网入口是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 基础地址是 https://taotoken.net/api 注意 API 地址后面不加 UTM 参数。具体操作分三步第一步打开控制台创建 API Key。访问 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 登录后在 API Keys 页面生成一个新的 Key复制保存好后面配置要用。第二步如果你用的是 Claude Code 或者类似的编码助手可以在配置里填入 TaoToken 的 API 地址和刚才生成的 Key。Claude Code 的接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 里面有详细的参数说明。第三步验证通道是否通。你可以用模型对话页面发一条测试消息地址是 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite 能正常返回就说明 Key 和 API 地址配置正确。如果你打算长期做 Oracle 开发或者跑 Agent 任务可以了解一下 Coding Plan入口在 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 它适合需要持续调用编码能力的场景。注意TaoToken 只是辅助通道不替代你的数据库客户端。游标代码的执行和验证还是在 SQL*Plus 或 SQL Developer 里完成。4. 可复制配置四种游标写法逐行拆解4.1 隐式游标用 SQL%FOUND 判断 DML 结果隐式游标最典型的用法是在 UPDATE 或 DELETE 之后通过SQL%FOUND和SQL%ROWCOUNT来判断操作是否命中数据。下面这段代码可以直接在 SQL*Plus 里跑SET SERVEROUTPUT ON; BEGIN UPDATE t_contract_master SET liability_state 1 WHERE policy_code 123456789; IF SQL%FOUND THEN DBMS_OUTPUT.PUT_LINE(更新成功影响行数 || SQL%ROWCOUNT); COMMIT; ELSE DBMS_OUTPUT.PUT_LINE(没有匹配的记录更新未生效); END IF; END; /这里的关键点是SQL%FOUND在 DML 语句执行后为 TRUE 表示至少影响了一行SQL%ROWCOUNT返回具体行数。注意隐式游标不需要你手动 OPEN 或 CLOSEOracle 自动管理。但如果你在SELECT ... INTO里返回了多行会直接抛ORA-01422: too_many_rows返回零行则抛ORA-01403: no_data_found这两个错误后面排查清单里会再提。4.2 显式游标四步法 %ROWTYPE 接收显式游标的标准流程是声明游标、打开游标、FETCH 提取、CLOSE 关闭。下面用%ROWTYPE来接收整行数据代码更简洁SET SERVEROUTPUT ON; DECLARE CURSOR cur_policy IS SELECT cm.policy_code, cm.applicant_id, cm.period_prem, cm.bank_code, cm.bank_account FROM t_contract_master cm WHERE cm.liability_state 2 AND cm.policy_type 1 AND cm.policy_cate IN (2,3,4) AND ROWNUM 5 ORDER BY cm.policy_code DESC; curPolicyInfo cur_policy%ROWTYPE; BEGIN OPEN cur_policy; LOOP FETCH cur_policy INTO curPolicyInfo; EXIT WHEN cur_policy%NOTFOUND; DBMS_OUTPUT.PUT_LINE(保单号 || curPolicyInfo.policy_code || 保费 || curPolicyInfo.period_prem); END LOOP; CLOSE cur_policy; EXCEPTION WHEN OTHERS THEN IF cur_policy%ISOPEN THEN CLOSE cur_policy; END IF; DBMS_OUTPUT.PUT_LINE(异常 || SQLERRM); END; /这段代码里有两个容易踩坑的地方。一是EXIT WHEN cur_policy%NOTFOUND必须放在 FETCH 之后否则最后一次 FETCH 拿不到数据还会继续处理空记录。二是异常处理里一定要判断%ISOPEN再 CLOSE不然游标已经关了你还去关就会触发ORA-01001。如果你不想用%ROWTYPE也可以声明多个标量变量逐个接收写法如下DECLARE CURSOR cur_policy IS SELECT policy_code, applicant_id, period_prem FROM t_contract_master WHERE liability_state 2 AND ROWNUM 5; v_policyCode t_contract_master.policy_code%TYPE; v_applicantId t_contract_master.applicant_id%TYPE; v_periodPrem t_contract_master.period_prem%TYPE; BEGIN OPEN cur_policy; LOOP FETCH cur_policy INTO v_policyCode, v_applicantId, v_periodPrem; EXIT WHEN cur_policy%NOTFOUND; DBMS_OUTPUT.PUT_LINE(v_policyCode || | || v_periodPrem); END LOOP; CLOSE cur_policy; END; /标量变量方式的好处是你可以只取需要的列内存占用更小缺点是列多了写起来啰嗦而且顺序必须和 SELECT 列表严格一致错位了就会报ORA-06504: ROWTYPE_MISMATCH。4.3 游标 FOR 循环最省心的遍历方式游标 FOR 循环把 OPEN、FETCH、CLOSE 都省了Oracle 自动帮你做代码量直接砍半SET SERVEROUTPUT ON; DECLARE CURSOR cur_policy IS SELECT policy_code, applicant_id, period_prem FROM t_contract_master WHERE liability_state 2 AND ROWNUM 5 ORDER BY policy_code DESC; BEGIN FOR rec_policy IN cur_policy LOOP DBMS_OUTPUT.PUT_LINE(保单号 || rec_policy.policy_code); END LOOP; END; /你甚至可以不声明游标直接在 FOR 循环里写子查询BEGIN FOR rec IN (SELECT ename, hiredate FROM emp ORDER BY hiredate DESC) LOOP DBMS_OUTPUT.PUT_LINE(姓名 || rec.ename || 入职日期 || rec.hiredate); END LOOP; END; /参数游标也很实用声明时带上参数循环时传入DECLARE CURSOR emp_cursor(dno NUMBER) IS SELECT ename, job FROM emp WHERE deptno dno; BEGIN FOR rec IN emp_cursor(20) LOOP DBMS_OUTPUT.PUT_LINE(姓名 || rec.ename || 岗位 || rec.job); END LOOP; END; /游标 FOR 循环里如果你想提前退出可以用EXIT WHEN配合%ROWCOUNT但注意在 FOR 循环中游标是隐式打开的你不能手动 CLOSE 它。4.4 REF CURSOR运行期动态决定结果集REF CURSOR 适合需要动态拼 SQL 或者把结果集传给其他程序的场景。先看无返回类型的写法SET SERVEROUTPUT ON; DECLARE TYPE ref_cursor_type IS REF CURSOR; ref_cursor ref_cursor_type; v1 NUMBER(6); v2 VARCHAR2(10); sqlStr VARCHAR2(500); BEGIN sqlStr : SELECT policy_code, bank_code FROM t_contract_master WHERE ROWNUM 5; OPEN ref_cursor FOR sqlStr; LOOP FETCH ref_cursor INTO v1, v2; EXIT WHEN ref_cursor%NOTFOUND; DBMS_OUTPUT.PUT_LINE(col1 || v1 || , col2 || v2); END LOOP; CLOSE ref_cursor; END; /有返回类型的 REF CURSOR 需要绑定到具体的表行类型DECLARE TYPE emp_cursor_type IS REF CURSOR RETURN emp%ROWTYPE; emp_cursor emp_cursor_type; emp_record emp%ROWTYPE; BEGIN OPEN emp_cursor FOR SELECT * FROM emp WHERE deptno 20; LOOP FETCH emp_cursor INTO emp_record; EXIT WHEN emp_cursor%NOTFOUND; DBMS_OUTPUT.PUT_LINE(姓名 || emp_record.ename || 工资 || emp_record.sal); END LOOP; CLOSE emp_cursor; END; /REF CURSOR 最大的坑是OPEN ... FOR里的 SQL 字符串如果拼错了编译期不会报错只有运行到 OPEN 那一行才会抛异常。所以动态 SQL 一定要在异常处理里捕获SQLERRM方便定位。4.5 批量提取BULK COLLECT 与 LIMIT 配合如果你要处理大量数据逐行 FETCH 效率太低可以用BULK COLLECT一次性拉取DECLARE CURSOR emp_cursor IS SELECT * FROM emp WHERE LOWER(job) clerk; TYPE emp_table_type IS TABLE OF emp%ROWTYPE; emp_table emp_table_type; BEGIN OPEN emp_cursor; FETCH emp_cursor BULK COLLECT INTO emp_table; CLOSE emp_cursor; FOR i IN 1 .. emp_table.COUNT LOOP DBMS_OUTPUT.PUT_LINE(姓名 || emp_table(i).ename || 工资 || emp_table(i).sal); END LOOP; END; /如果数据量特别大一次性拉取可能撑爆内存这时候用LIMIT分批DECLARE CURSOR emp_cursor IS SELECT * FROM emp; TYPE emp_array_type IS VARRAY(5) OF emp%ROWTYPE; emp_array emp_array_type; BEGIN OPEN emp_cursor; LOOP FETCH emp_cursor BULK COLLECT INTO emp_array LIMIT 5; FOR i IN 1 .. emp_array.COUNT LOOP DBMS_OUTPUT.PUT_LINE(姓名 || emp_array(i).ename); END LOOP; EXIT WHEN emp_cursor%NOTFOUND; END LOOP; CLOSE emp_cursor; END; /LIMIT 5表示每次最多取 5 行配合EXIT WHEN %NOTFOUND循环直到取完。这种写法在数据迁移场景里特别常用既能控制内存又比逐行 FETCH 快很多。5. 验证请求与成功结果在 SQL*Plus 和 SQL Developer 里跑通代码写完了怎么确认它真的跑对了分两个环境说。在 SQL*Plus 里第一步是确保SERVEROUTPUT打开否则DBMS_OUTPUT.PUT_LINE的输出你根本看不到SET SERVEROUTPUT ON SIZE UNLIMITED;然后把你写的 PL/SQL 块粘贴进去以/结尾执行。如果代码里有绑定变量比如dnoSQL*Plus 会弹出提示让你输入值。执行成功后你应该在屏幕上看到类似这样的输出保单号P20240101001保费3500.00 保单号P20240101002保费4200.50 PL/SQL procedure successfully completed.如果看到PL/SQL procedure successfully completed.但没有任何数据输出先检查SERVEROUTPUT是否打开再检查 WHERE 条件是否真的命中了数据。在 SQL Developer 里操作更直观。打开一个 Worksheet把代码粘贴进去注意两点一是确保工具栏上的DBMS Output面板已经打开菜单 View - DBMS Output二是点击绿色的运行按钮或者按 F5 执行。SQL Developer 会自动处理SET SERVEROUTPUT你不需要手动加。执行后DBMS Output 面板里会显示你的输出脚本输出窗口会显示编译和执行状态。如果你想验证 REF CURSOR 的动态 SQL 是否正确可以在 OPEN 之前先把sqlStr打印出来DBMS_OUTPUT.PUT_LINE(SQL || sqlStr);这样如果 OPEN 报错你能立刻看到拼出来的 SQL 长什么样快速定位是列名写错还是表名不对。对于批量提取的代码验证时建议先用小数据量测试比如ROWNUM 10确认逻辑没问题再放开全量。数据迁移场景里我习惯先跑SELECT COUNT(*)确认源表数据量再决定用逐行 FETCH 还是 BULK COLLECT。6. 本篇常见错排查ORA-01001、ORA-06511、ORA-06504 对照清单游标相关的报错翻来覆去就那几个下面按错误代码整理排查思路。ORA-01001: invalid cursor通常是你试图 FETCH 或 CLOSE 一个没有打开的游标。检查点OPEN 语句是否真的执行到了有没有在异常处理里提前 CLOSE 了游标 FOR 循环里你是不是手动写了 CLOSEFOR 循环的游标是隐式打开的手动 CLOSE 会报这个错。ORA-06511: cursor already open是你重复 OPEN 了同一个游标。常见于循环里反复 OPEN 没有 CLOSE或者异常处理后没有正确关闭就再次 OPEN。解决办法是确保每次 OPEN 之前游标处于关闭状态或者在 OPEN 之前加IF cur%ISOPEN THEN CLOSE cur; END IF;。ORA-06504: ROWTYPE_MISMATCH是 FETCH INTO 的变量类型或数量和游标 SELECT 列表不匹配。检查 SELECT 的列数、顺序、类型是否和 INTO 后面的变量一一对应。用%ROWTYPE可以避免大部分这类问题。ORA-01422: too_many_rows出现在SELECT ... INTO返回多行时。隐式游标只适合单行查询多行必须用显式游标或游标 FOR 循环。ORA-01403: no_data_found是SELECT ... INTO没有返回任何行。如果你不确定是否有数据要么用聚合函数包一层要么改用显式游标遍历。ORA-01722: invalid_number通常发生在动态 SQL 拼接时字符串里的数字被当成了列名或者类型转换失败。检查sqlStr里的引号是否配对数字是否被单引号包成了字符串。ORA-01012: not_logged_on是数据库连接断了。检查你的会话是否超时或者 SQL*Plus 是否还连着。下面这张表帮你快速对照错误代码含义首要排查点ORA-01001无效游标是否 OPEN 后再 FETCH/CLOSEORA-06511游标已打开是否重复 OPEN 未 CLOSEORA-06504类型不匹配FETCH INTO 变量与 SELECT 列是否对齐ORA-01422返回多行SELECT INTO 是否只返回一行ORA-01403无数据SELECT INTO 是否命中数据ORA-01722无效数字动态 SQL 拼接的引号和类型ORA-01012未登录数据库连接是否存活还有一个容易被忽略的点在游标 FOR 循环里用%ROWCOUNT做退出条件时%ROWCOUNT统计的是已经 FETCH 的行数不是剩余行数。如果你想取前 N 行用EXIT WHEN cur%ROWCOUNT N是可行的但要注意 FOR 循环的游标是隐式的%ROWCOUNT的引用方式和你手动 OPEN 的游标一样。如果你在排查过程中需要快速查文档或者让模型帮你分析报错可以用 TaoToken 的模型对话入口 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite 把报错信息和代码片段贴进去通常能很快定位到问题。API Keys 的管理在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 需要的时候直接翻。最后说一个实战经验写游标的时候养成在异常处理里统一 CLOSE 的习惯并且用%ISOPEN做保护。我见过太多因为异常分支漏了 CLOSE 导致游标泄漏、会话卡死的案例。另外能用游标 FOR 循环就别手动 OPEN/FETCH/CLOSE代码越短出错的地方越少。数据迁移场景优先考虑 BULK COLLECT LIMIT逐行 FETCH 只适合数据量小或者需要复杂行间逻辑的情况。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。