sys_refcursor是oracle预定义的弱类型游标变量,本质为指向结果集的指针;与普通声明式游标不同,它可作为out参数直接返回给客户端,由调用方消费,不可在pl/sql中fetch或close,且必须用open...for打开,不支持字符串拼接sql。

什么是 SYS_REFCURSOR,它和普通游标有什么区别?
SYS_REFCURSOR 是 Oracle 提供的预定义 REF CURSOR 类型,本质是一个指向查询结果集的指针,不是数据本身。它和声明式游标(如 DECLARE cur1 SYS_REFCURSOR)不同:后者必须先 OPEN 再 FETCH,而 SYS_REFCURSOR 可以直接作为存储过程 OUT 参数返回给调用方(比如 Java、PL/SQL 块或 Python cx_Oracle),由外部程序控制遍历。
关键点在于:SYS_REFCURSOR 不保存数据,不占用服务端长期资源,但也不能在 PL/SQL 中直接 FETCH —— 它必须被打开后传出去,由调用者消费。
在存储过程中如何正确声明和打开 SYS_REFCURSOR?
常见错误是忘记 OPEN,或误用静态 SQL 字符串拼接导致 SQL 注入或语法错误。正确做法是用 OPEN ... FOR 直接绑定查询语句,支持变量和动态条件,但不要手动拼接 SQL。
- 声明为
OUT参数:PROCEDURE get_emp_data(p_dept_id IN NUMBER, p_result OUT SYS_REFCURSOR) - 在过程体内必须执行
OPEN p_result FOR SELECT * FROM emp WHERE deptno = p_dept_id - 不能写成
OPEN p_result FOR 'SELECT * FROM emp WHERE deptno = ' || p_dept_id—— 这会报 ORA-06550,且有注入风险 - 如果需要动态表名或列名,才用
EXECUTE IMMEDIATE+OPEN ... FOR,但必须严格校验输入(例如白名单判断表名)
Java 或 Python 调用时怎么获取结果?
客户端拿到的是一个游标句柄,不是 ResultSet 或 list。具体行为取决于驱动:Oracle JDBC 默认把 SYS_REFCURSOR 映射为 ResultSet;cx_Oracle 则直接返回可迭代对象。容易踩的坑是没关闭游标或提前关闭连接。
- JDBC 示例中,必须用
CallableStatement.registerOutParameter(2, OracleTypes.CURSOR)注册类型,再execute()后调用getResultSet() - Python cx_Oracle 里,直接
cursor.callproc('get_emp_data', [dept_id, cursor]),第二个参数传入已创建的cursor对象,之后用该对象fetchall() - 无论哪种语言,游标生命周期依赖数据库连接 —— 连接关闭前必须读完或显式关闭游标,否则可能触发 ORA-01001(invalid cursor)
为什么有时候返回空结果却没报错?
因为 SYS_REFCURSOR 打开的是一个“空结果集”,不是异常。比如 WHERE 条件不匹配、表为空、甚至 SELECT * FROM DUAL WHERE 1=0 都合法,只是返回零行 —— 调用方看到的是空 ResultSet 或空 list,不是 null 或异常。
真正要警惕的是运行时错误:比如查询中引用了不存在的列(ORA-00904)、权限不足(ORA-00942)或打开时发生异常(未捕获的 EXCEPTION)。这些才会中断执行并抛出错误,而空结果是完全正常的行为。
别指望靠“结果是否为空”来判断逻辑是否成功;需要额外输出状态码或计数字段,或者用 SQL%ROWCOUNT 在 PL/SQL 里记录影响行数再通过另一个 OUT 参数返回。











