必须用多个OUT SYS_REFCURSOR参数声明存储过程以返回多个结果集,个数编译时固定、顺序严格匹配调用端绑定;每个游标必须OPEN(含空结果集占位),且仅支持OUT模式;C#需按序绑定RefCursor类型参数并用NextResult()遍历,Java须注册Types.OTHER并在同连接内及时处理ResultSet。

必须用多个 OUT SYS_REFCURSOR 参数声明
Oracle 存储过程返回多个结果集,不能靠一个游标变量“装多个查询”,也不能用动态数量的游标。PL/SQL 层面必须显式声明多个 OUT SYS_REFCURSOR 参数,个数在编译时就固定,顺序即调用时绑定顺序。
常见错误是只声明一个游标参数、却在过程里试图多次 OPEN;或漏掉某个 OPEN 语句,导致 C# 或 Java 端报 ORA-01008: not all variables bound。
- 每个
OUT SYS_REFCURSOR都要对应一次OPEN ... FOR SELECT - 即使某结果集为空,也得
OPEN p_cursor FOR SELECT * FROM DUAL WHERE 1=0占位 - 不支持用
IN或IN OUT模式传游标输出参数——必须是OUT
C# 调用时按位置绑定 RefCursor,不是按名字
C# 的 OracleCommand 不认参数名,只按存储过程中声明的顺序匹配 OracleParameter。写错顺序、少加一个参数、类型没设成 OracleDbType.RefCursor,都会导致数据错位或空结果。
比如存储过程定义为 p_users OUT SYS_REFCURSOR, p_orders OUT SYS_REFCURSOR, p_logs OUT SYS_REFCURSOR,C# 就必须严格按这个顺序 add 三个 OracleParameter,且每个都设 Direction = ParameterDirection.Output 和 OracleDbType.RefCursor。
- 别写
cmd.Parameters.Add("p_user", ...)(少了个 s)——名字错 Oracle 不报错,但第二参数内容会跑到第一个DataReader里 - 必须调用
ExecuteReader()或ExecuteNonQuery()后,再用NextResult()切换结果集 - 没调
NextResult()就反复Read(),只会读第一个结果集
Java 端注册 Types.OTHER,且 ResultSet 必须同连接处理完
Java JDBC 驱动把 SYS_REFCURSOR 映射为 java.sql.Types.OTHER,不是 Types.RESULT_SET(那是 JDBC 4.1+ 的伪类型,Oracle 驱动不支持)。漏注册或类型填错,直接抛 SQLException: Invalid column type。
更关键的是生命周期:REF CURSOR 是服务端打开的指针,和当前数据库连接强绑定。一旦连接关闭(比如 Spring @Transactional 方法结束)、或 ResultSet 被跨连接复用,立刻报 ORA-01001: invalid cursor。
- 必须用
cs.registerOutParameter(2, Types.OTHER)(索引从 1 开始,第二个参数就是游标) - 必须用
cs.getResultSet()获取,不是getObject() - 拿到
ResultSet后,要在同一连接上调用完next()并读完所有行,再关闭它 - 连接池(如 HikariCP)里,绝不能把 ResultSet 持有到连接归还之后
别碰自定义强类型游标,SYS_REFCURSOR 是唯一稳妥选择
有人想用 TYPE my_cursor_type IS REF CURSOR RETURN users%ROWTYPE,这在 PL/SQL 内部可行,但 Java/C# JDBC 驱动根本不识别——调用时直接报 ORA-06550: PLS-00382: expression is of wrong type。
SYS_REFCURSOR 是 Oracle 预定义的弱类型游标,JDBC 驱动专为它做了适配。只要 PL/SQL 层用它、客户端按规范注册和读取,就能稳定工作。其他方式(如表类型、管道函数、嵌套表)虽然也能“返回多行”,但本质不是结果集流式传输,不适合前端分页、逐行消费等典型场景。
真正容易被忽略的点是:游标没及时 close,DB 端长期占用内存;尤其大数据量时,可能拖慢整个实例。


















