news 2026/10/8 12:02:18

Oracle 游标到底怎么用?从显式游标到游标 FOR 循环的完整实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Oracle 游标到底怎么用?从显式游标到游标 FOR 循环的完整实践

1. 从一次批量更新说起:Oracle 游标到底解决什么问题

刚接触 Oracle PL/SQL 的时候,很多人会卡在同一个地方:SQL 一次只能处理一个结果集,但业务逻辑偏偏要一行一行地判断、计算、再写回去。比如你有一张订单表,需要根据每笔订单的金额决定是否打标、是否发券、是否写日志——这些动作没法用一条 UPDATE 搞定,必须把结果集"拿在手里"逐行处理。这时候游标(Cursor)就登场了。

游标本质上是一个指向查询结果集的指针。你可以把它想象成一根手指,按在查询结果的第一行上,然后一行一行往下滑。每滑一行,你就能把当前行的字段读进变量里,做任意逻辑判断。Oracle 里的游标分三类:显式游标、隐式游标、游标 FOR 循环。显式游标需要你手动声明、打开、取值、关闭,控制力最强;隐式游标由 Oracle 自动管理,适合单行操作;游标 FOR 循环则是语法糖,把打开、取值、关闭全包了,写起来最省心。

这篇文章面向刚上手 Oracle PL/SQL 的开发者,我会用一张真实的测试表,把三类游标的完整链路走一遍。每一步都给可复制的建表语句和 PL/SQL 块,并且告诉你执行后应该看到什么输出。更重要的是,我会讲清楚什么时候该用游标、什么时候该改用 BULK COLLECT 批量绑定——因为游标用错了场景,性能会差出几十倍。如果你正在写存储过程做批量数据处理,这篇内容可以直接对照着改代码。

先明确一个检索词:Oracle 游标遍历结果集。你在搜索时可能用的是"Oracle 游标用法""PL/SQL 游标 FOR 循环""显式游标 fetch"这类词,核心都是同一件事——怎么把查询结果一行行取出来处理。下面从建表开始。

2. 显式游标:声明、打开、取值、关闭的完整链路

2.1 建一张测试表并灌入数据

在 SQL*Plus 或 SQL Developer 里执行:

CREATE TABLE testA ( id NUMBER, name VARCHAR2(20) ); INSERT INTO testA VALUES (1, 'zhangsan'); INSERT INTO testA VALUES (2, 'lisi'); INSERT INTO testA VALUES (3, 'wangwu'); COMMIT;

这张表只有三行,方便你对照输出。实际业务表可能几百万行,但游标的操作逻辑完全一样。

2.2 显式游标的四个动作

显式游标的标准写法分四步:DECLARE 声明、OPEN 打开、FETCH 取值、CLOSE 关闭。看一个完整例子:

DECLARE CURSOR c_test IS SELECT id, name FROM testA; v_id testA.id%TYPE; v_name testA.name%TYPE; BEGIN OPEN c_test; LOOP FETCH c_test INTO v_id, v_name; EXIT WHEN c_test%NOTFOUND; DBMS_OUTPUT.PUT_LINE(v_id || ' ' || v_name); END LOOP; CLOSE c_test; END; /

执行前记得打开输出:SET SERVEROUTPUT ON;。你会看到:

1 zhangsan 2 lisi 3 wangwu

这里有几个关键点。%TYPE让变量类型自动跟随列类型,改表结构时不用改代码。%NOTFOUND是游标属性,当 FETCH 没有取到数据时返回 TRUE,用来退出循环。注意 FETCH 和 EXIT WHEN 的顺序——必须先 FETCH 再判断,否则会多输出一行或漏掉最后一行。

2.3 游标的四个属性

属性含义典型用途
%FOUND上一次 FETCH 是否取到数据判断是否继续循环
%NOTFOUND上一次 FETCH 是否没取到数据退出循环
%ROWCOUNT到目前为止取了多少行统计处理条数
%ISOPEN游标是否处于打开状态关闭前判断,避免异常

%ROWCOUNT在批量处理时特别有用。比如你想每处理 1000 行提交一次,就可以用IF c_test%ROWCOUNT MOD 1000 = 0 THEN COMMIT; END IF;。

2.4 带参数的显式游标

实际业务里查询条件往往是动态的。显式游标支持参数:

DECLARE CURSOR c_test(p_min_id NUMBER) IS SELECT id, name FROM testA WHERE id >= p_min_id; v_id testA.id%TYPE; v_name testA.name%TYPE; BEGIN OPEN c_test(2); LOOP FETCH c_test INTO v_id, v_name; EXIT WHEN c_test%NOTFOUND; DBMS_OUTPUT.PUT_LINE(v_id || ' ' || v_name); END LOOP; CLOSE c_test; END; /

输出只有2 lisi和3 wangwu。参数化游标的好处是同一个游标定义可以复用于不同条件,不用为每个查询写一遍声明。

2.5 什么时候该用显式游标

显式游标适合这些场景:需要在循环中间做复杂逻辑判断、需要手动控制提交频率、需要根据%ROWCOUNT做分批处理、或者需要在打开游标前做动态 SQL 拼接。如果你只是简单遍历,往下看游标 FOR 循环会更省事。

3. 游标 FOR 循环:最省心的遍历方式

3.1 基本写法

游标 FOR 循环把声明、打开、取值、关闭全自动化了。你只需要定义游标,然后FOR 变量 IN 游标 LOOP:

DECLARE CURSOR c_test IS SELECT id, name FROM testA; BEGIN FOR v_test IN c_test LOOP DBMS_OUTPUT.PUT_LINE(v_test.id || ' ' || v_test.name); END LOOP; END; /

输出和显式游标一样。注意v_test不需要你声明,Oracle 自动把它定义成c_test%ROWTYPE。你直接用v_test.id、v_test.name访问字段。

3.2 隐式游标 FOR 循环

更省事的写法是连游标声明都省掉,直接把 SELECT 写在 FOR 里:

BEGIN FOR v_test IN (SELECT id, name FROM testA) LOOP DBMS_OUTPUT.PUT_LINE(v_test.id || ' ' || v_test.name); END LOOP; END; /

这种写法叫隐式游标 FOR 循环。Oracle 在背后帮你做了所有脏活。适合一次性遍历、不需要复用游标定义的场景。

3.3 游标 FOR 循环的注意事项

第一,循环变量是只读的。你不能在循环体里给v_test.id赋值,想改数据得用 UPDATE 语句。第二,游标 FOR 循环只适用于静态 SQL,动态 SQL 得用显式游标加OPEN ... FOR。第三,循环结束后游标自动关闭,你不需要也不能手动 CLOSE。

3.4 动态 SQL 与显式游标的配合

当 SQL 语句本身是运行时拼出来的,就得用动态游标。Oracle 提供SYS_REFCURSOR:

DECLARE v_cur SYS_REFCURSOR; v_id testA.id%TYPE; v_name testA.name%TYPE; v_sql VARCHAR2(200); BEGIN v_sql := 'SELECT id, name FROM testA WHERE id > :1'; OPEN v_cur FOR v_sql USING 1; LOOP FETCH v_cur INTO v_id, v_name; EXIT WHEN v_cur%NOTFOUND; DBMS_OUTPUT.PUT_LINE(v_id || ' ' || v_name); END LOOP; CLOSE v_cur; END; /

OPEN ... FOR支持绑定变量,用USING传参,能有效防止 SQL 注入。动态游标必须手动关闭,否则会耗尽OPEN_CURSORS参数限制。

3.5 执行计划验证

想确认游标查询有没有走索引,可以在 SQL Developer 里按 F5 看执行计划,或者:

EXPLAIN PLAN FOR SELECT id, name FROM testA WHERE id > 1; SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);

如果testA数据量大且id有索引,应该看到 INDEX RANGE SCAN。全表扫描在几百万行时会拖慢游标遍历。

4. 隐式游标与批量绑定:什么时候该放弃逐行 FETCH

4.1 隐式游标的 SQL 属性

每次执行 DML 语句(INSERT/UPDATE/DELETE)或单行 SELECT INTO,Oracle 都会自动创建一个隐式游标。你可以用SQL%FOUND、SQL%ROWCOUNT等属性获取上次执行的信息:

BEGIN UPDATE testA SET name = 'zhaoliu' WHERE id = 1; IF SQL%FOUND THEN DBMS_OUTPUT.PUT_LINE('更新了 ' || SQL%ROWCOUNT || ' 行'); END IF; END; /

输出更新了 1 行。隐式游标不需要声明和关闭,适合单行操作。但如果你在循环里反复执行单行 DML,性能会很差——每次都有上下文切换开销。

4.2 逐行 FETCH 的性能陷阱

假设testA有 100 万行,你用显式游标逐行 FETCH 再逐行 UPDATE,会发生什么?每次 FETCH 是一次 PL/SQL 到 SQL 引擎的切换,每次 UPDATE 又是一次。100 万次切换,耗时可能几分钟甚至更久。

我试过在一张 50 万行的表上做逐行更新,跑了将近 4 分钟。改成 BULK COLLECT 后,降到 3 秒以内。

4.3 BULK COLLECT 批量取值

BULK COLLECT 一次性把结果集批量取进集合:

DECLARE TYPE t_id IS TABLE OF testA.id%TYPE; TYPE t_name IS TABLE OF testA.name%TYPE; v_ids t_id; v_names t_name; CURSOR c_test IS SELECT id, name FROM testA; BEGIN OPEN c_test; FETCH c_test BULK COLLECT INTO v_ids, v_names; CLOSE c_test; FOR i IN 1 .. v_ids.COUNT LOOP DBMS_OUTPUT.PUT_LINE(v_ids(i) || ' ' || v_names(i)); END LOOP; END; /

BULK COLLECT INTO一次把所有行取进内存集合。对于大结果集,可以配合LIMIT分批:

LOOP FETCH c_test BULK COLLECT INTO v_ids, v_names LIMIT 1000; EXIT WHEN v_ids.COUNT = 0; -- 处理这 1000 行 END LOOP;

4.4 FORALL 批量 DML

取值用 BULK COLLECT,写回用 FORALL:

DECLARE TYPE t_id IS TABLE OF testA.id%TYPE; v_ids t_id := t_id(1, 2, 3); BEGIN FORALL i IN 1 .. v_ids.COUNT UPDATE testA SET name = 'batch_' || v_ids(i) WHERE id = v_ids(i); COMMIT; END; /

FORALL 把多条 DML 打包成一次发送,减少上下文切换。注意 FORALL 里不能写复杂逻辑,只能跟单条 DML。

4.5 选型对照表

场景推荐方式原因
单行查询赋值SELECT INTO隐式游标自动管理
小结果集遍历(<1万行)游标 FOR 循环代码简洁,可读性好
需要复杂逐行逻辑显式游标控制力强,可手动提交
大结果集批量处理BULK COLLECT + FORALL减少上下文切换,性能最优
动态 SQL 遍历SYS_REFCURSOR支持运行时拼接

5. 常见报错排查:从 ORA-01001 到 ORA-06550

5.1 ORA-01001: invalid cursor

这个错通常是因为你 FETCH 了一个没打开的游标,或者 CLOSE 了两次。检查 OPEN 和 CLOSE 是否配对。用%ISOPEN判断:

IF c_test%ISOPEN THEN CLOSE c_test; END IF;

5.2 ORA-06550: line X, column Y

PL/SQL 编译错误,通常是语法问题。比如EXIT WHEN c_test%NOTFOUND写成了EXIT WHEN c_test.NOTFOUND,或者漏了分号。把错误行号对应到代码里逐行检查。

5.3 ORA-01403: no data found

SELECT INTO 没查到数据时抛这个错。用隐式游标属性判断:

BEGIN SELECT name INTO v_name FROM testA WHERE id = 999; EXCEPTION WHEN NO_DATA_FOUND THEN DBMS_OUTPUT.PUT_LINE('没找到记录'); END; /

5.4 ORA-01422: exact fetch returns more than requested number of rows

SELECT INTO 返回多行。要么加 WHERE 条件限定一行,要么改用游标遍历。

5.5 ORA-01000: maximum open cursors exceeded

打开的游标没关闭,累积超过OPEN_CURSORS限制。检查所有OPEN是否都有对应的CLOSE。用这个查询看当前打开的游标:

SELECT COUNT(*) FROM v$open_cursor WHERE user_name = USER;

5.6 游标 FOR 循环里改数据不生效

游标 FOR 循环的循环变量是只读快照,你在循环体里 UPDATE 了表,但循环变量不会刷新。如果需要看到最新值,得重新查询。

5.7 动态游标绑定变量类型不匹配

OPEN ... FOR ... USING时,USING 的变量类型要和 SQL 里的占位符匹配。比如:1是 NUMBER,你传了字符串就会报 ORA-01722。检查绑定变量类型。

6. 把游标用对:从能跑到跑得快的实践建议

游标本身不难,难的是选对场景。我的经验是:能用一条 SQL 解决的,绝不用游标;必须逐行处理的,优先游标 FOR 循环;数据量超过一万行的,直接上 BULK COLLECT + FORALL。显式游标留给需要手动控制提交频率或动态 SQL 的场景。

另外几个实用技巧。第一,游标查询尽量走索引,用EXPLAIN PLAN确认执行计划。第二,批量处理时用LIMIT分批 FETCH,避免一次性把几百万行读进 PGA 导致内存溢出。第三,循环里的 COMMIT 频率别太高,每 1000 到 5000 行提交一次比较平衡。第四,动态 SQL 一定要用绑定变量,别用字符串拼接,既防注入又提升性能。

如果你在写存储过程时拿不准该用哪种游标,可以先按最简单的方式写出来,跑通逻辑后再用 BULK COLLECT 优化性能瓶颈。代码正确性永远优先于性能,先跑对再跑快。

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

2026 企业 AI 办公工具选型指南:面向团队落地的评估框架

不少企业在调研AI办公工具的初期&#xff0c;很容易陷入几个典型的选型误区&#xff1a;有人把功能列表的长度作为核心判断标准&#xff0c;数谁家支持的功能点更多就选谁&#xff0c;上线之后才发现大部分功能团队根本用不上&#xff1b;有人只盯着采购成本做决策&#xff0c;…

作者头像 李华
网站建设 2026/10/8 11:59:11

Agent-Reach 实战:构建安全可落地的命令行执行型 AI Agent

1. 从命令行到智能体&#xff1a;Agent-Reach 到底在解决什么问题 第一次看到 Agent-Reach 这个名字&#xff0c;我下意识把它拆成了两半&#xff1a;Agent 和 Reach。Agent 是智能体&#xff0c;Reach 是触达、延伸、够得着。合在一起&#xff0c;它想表达的意思其实很直白——…

作者头像 李华
网站建设 2026/10/8 11:58:42

SSM经典项目雅博书城本地部署与功能扩展实战

简介&#xff1a;本资源是一套高分通过的Java毕业设计实战项目——基于SSM框架的雅博书城在线系统&#xff0c;面向计算机专业本科生毕设选题、课程设计及Java初学者项目实训需求&#xff0c;有效解决缺乏完整可运行电商类系统案例的问题。压缩包共1342个文件&#xff0c;涵盖3…

作者头像 李华
网站建设 2026/10/8 11:58:00

SSM与微信小程序健身预约系统:从源码跑通到答辩避坑全指南

简介&#xff1a;基于SSM与微信小程序的健身管理毕业设计项目&#xff0c;面向计算机专业毕业生及需要完整项目范例的开发者&#xff0c;覆盖健身课程、教练预约、订单管理等典型业务&#xff0c;可直接用于毕业设计或课程设计参考。压缩包共970个文件&#xff0c;包含142个Jav…

作者头像 李华
网站建设 2026/10/8 11:57:37

AI编程助手上下文控制:context-mode插件如何精准投喂代码

如果你跟我一样把 AI 编程助手当成日常搭档&#xff0c;大概也经历过这样的崩溃瞬间&#xff1a;让 AI 改一个函数&#xff0c;它把整个仓库的无关代码全读进去&#xff0c;然后一本正经地给你一个根本跑不起来的方案&#xff1b;反过来&#xff0c;只给它当前文件的三十行&…

作者头像 李华
网站建设 2026/10/8 11:56:34

Java+MooseFS网盘后端源码包解析:分布式文件系统落地实践

简介&#xff1a;这是基于Java与Moosefs的分布式文件系统设计与实现完整项目&#xff0c;面向高校计算机专业毕业设计、课程设计或分布式系统入门学习者。资源将服务端源码、客户端逻辑与项目文档整合为一体&#xff0c;可帮助读者理解分布式文件存储架构、元数据管理及Java多模…

作者头像 李华