必须用SYS_REFCURSOR而非自定义REF CURSOR,因其是弱类型,不校验结果集结构,可跨作用域传递并动态打开任意查询;自定义REF CURSOR为强类型,绑定固定列结构,不匹配即报PLS-00382错,且客户端和JDBC原生支持SYS_REFCURSOR。

SYS_REFCURSOR 是唯一能跨作用域返回结果集的游标类型,普通显式游标(如 CURSOR c IS SELECT ...)无法作为 OUT 参数传出。
为什么必须用 SYS_REFCURSOR 而不是自定义 REF CURSOR 类型
自定义 REF CURSOR(如 TYPE mycur IS REF CURSOR)是强类型的:它绑定到特定的查询结构(列名、数量、类型),一旦声明,只能打开匹配该结构的查询。而 SYS_REFCURSOR 是 Oracle 内置的弱类型引用游标,不校验结果集结构,同一变量可先后 OPEN FOR SELECT id, name FROM t1 和 OPEN FOR SELECT x, y, z FROM t2。
常见错误现象:PLS-00382: expression is of wrong type,往往是因为误用了自定义类型却传入了结构不一致的查询。
- 客户端工具(SQL Developer、Toad)和 JDBC 都原生识别
SYS_REFCURSOR,无需额外注册类型 - 若用自定义类型,包体中声明与调用方变量类型必须严格一致,极易因字段增减或顺序调整导致编译失败
- 动态 SQL 场景下(
EXECUTE IMMEDIATE+OPEN ... FOR),只有SYS_REFCURSOR支持运行时任意结构
OUT SYS_REFCURSOR 的声明与打开位置必须严格分离
参数方向必须是 OUT,且 OPEN ... FOR 语句只能出现在 BEGIN 块内,不能放在声明区或 EXCEPTION 中(除非重开)。
错误写法示例:
CREATE OR REPLACE PROCEDURE bad_proc(p_out OUT SYS_REFCURSOR) IS BEGIN -- ❌ 错误:这里不能直接 SELECT INTO 或赋值 p_out := SELECT * FROM dual; -- 编译报错 PLS-00382 END;
正确写法:
CREATE OR REPLACE PROCEDURE good_proc(p_dept_id IN NUMBER, p_out OUT SYS_REFCURSOR) IS
BEGIN
-- ✅ 必须用 OPEN FOR,且查询可含绑定变量
OPEN p_out FOR
SELECT id, name, salary
FROM users
WHERE dept_id = p_dept_id AND status = 'ACTIVE';
END;- 不能用
SELECT ... INTO给SYS_REFCURSOR赋值——那是给标量或记录赋值的语法 - 查询中可安全使用输入参数(如
p_dept_id),Oracle 自动做绑定,避免 SQL 注入 - 若需拼接动态 SQL 字符串(如根据条件加 WHERE 子句),必须配合
EXECUTE IMMEDIATE,但注意字符串拼接风险
调用方如何安全获取并遍历结果(以 PL/SQL 匿名块为例)
不能在另一个存储过程中直接 FETCH ——因为 OUT SYS_REFCURSOR 返回的是句柄,不是本地游标变量。必须先执行过程拿到句柄,再对句柄操作。
典型匿名块调用方式:
BEGIN get_user_data(p_dept_id => 10, p_result => :cur_out); END;
此时 :cur_out 是 SQL*Plus / SQL Developer 的绑定变量,类型需设为 CURSOR;在工具 Variables 面板点 Value 栏图标即可展开查看。
- 若要在 PL/SQL 块中进一步处理,需声明同类型变量接收,例如:
l_cur SYS_REFCURSOR,再用FETCH l_cur INTO ... -
%ROWTYPE必须与查询字段完全匹配,否则触发ORA-01007: variable not in select list—— 这是最常被忽略的运行时错误 - 记得
CLOSE游标(尤其在异常分支里),否则可能造成会话级游标泄漏
JDBC 中注册与获取 SYS_REFCURSOR 的关键步骤
JDBC 不像 PL/SQL 那样自动识别输出游标,必须显式注册类型并转换为 ResultSet。
核心三步不可省略:
- 预编译时用
{ call proc_name(?, ?) }占位符 -
cst.registerOutParameter(2, oracle.jdbc.OracleTypes.CURSOR)—— 第二个参数位置注册为游标类型 -
ResultSet rs = (ResultSet) cst.getObject(2)—— 不能用getResultSet(),否则在某些驱动版本会抛ClassCastException
容易踩的坑:
如果数据库端用了自定义 REF CURSOR 类型(如 pkg.mycursor),JDBC 必须用 registerOutParameter(2, Types.OTHER) 并传入类型名字符串,但兼容性差;SYS_REFCURSOR 则统一用 CURSOR 常量,最稳妥。
最后提醒:游标本身不持有数据,只是指向结果集的指针。真正 fetch 时才从服务器拉数据——这意味着网络延迟、fetch 大量行时的内存占用、以及未及时 close 导致的服务器资源滞留,都是真实压力点,不能只盯着“能返回”就结束。


















