二次执行代码触发ORA-01001错误,求排查解决方案
你的问题核心是游标泄漏,而且根源大概率在PL/SQL存储过程里,扩大open_cursor参数只是延缓问题,无法从根本上解决。下面一步步拆解问题并给出解决办法:
1. 先搞懂ORA-01001为什么会触发
ORA-01001是"无效游标"错误,常见场景包括:
- 引用了已经关闭的游标
- 游标资源没有被正确释放,导致数据库游标耗尽,新的游标请求无法被分配
- 客户端或服务端的游标管理逻辑有漏洞
结合你的场景,首次执行正常、第二次报错,说明每次执行都会留下未释放的游标,积累到第二次就触发了阈值(哪怕你扩大了open_cursor,只是次数变多才会报错而已)。
2. 定位存储过程的问题
你的PL/SQL存储过程用了DBMS_SQL包处理动态SQL,这里藏着一个容易忽略的坑:
当你调用DBMS_SQL.to_refcursor(v_dyn_cursor)把DBMS_SQL游标转换成REF CURSOR输出时,Oracle不会自动关闭原来的DBMS_SQL游标句柄。每次调用存储过程,都会创建一个新的v_dyn_cursor,但这个游标资源从来没被释放,直接导致了服务端的游标泄漏。
验证这个问题很简单:
执行存储过程前后,查询数据库的开放游标数量:
SELECT COUNT(*) FROM V$OPEN_CURSORS WHERE USER_NAME = '你的数据库用户名';
每次调用后数量都会增加,就实锤了游标泄漏。
3. 修复存储过程的最优方案
其实你的场景完全不需要用DBMS_SQL,直接用REF CURSOR的动态SQL语法就行,既简洁又不会有泄漏问题:
procedure procedure( i_inv IN NUMBER, p_cursor OUT sys_refcursor ) as vSQL varchar2(32000) := ''; begin vSQL := 'SELECT 1 FROM DUAL'; -- 直接用OPEN...FOR打开动态SQL到输出游标 OPEN p_cursor FOR vSQL; end;
如果你的实际SQL比示例复杂(比如需要绑定变量),也可以用绑定变量的方式:
vSQL := 'SELECT * FROM your_table WHERE inv = :inv_val'; OPEN p_cursor FOR vSQL USING i_inv;
4. 同时检查客户端C#代码的资源释放
虽然问题根源在存储过程,但客户端代码的资源管理也不能马虎,否则也可能加重游标泄漏:
你当前的代码里OracleDataReader没有被正确关闭,建议用using语句自动管理资源(会自动调用Dispose()和Close()):
public static void GetData() { // 用using包裹OracleCommand,确保自动释放 using (OracleCommand cmd = new OracleCommand("procedure", DBConnection.EnsureConnectionToDB())) { cmd.CommandType = CommandType.StoredProcedure; cmd.Parameters.Add("i_inv", OracleDbType.Decimal, 1, ParameterDirection.Input); var cursorParam = cmd.Parameters.Add("p_cursor", OracleDbType.RefCursor); cursorParam.Direction = ParameterDirection.Output; // 用using包裹OracleDataReader,确保读取完成后关闭游标 using (OracleDataReader reader = cmd.ExecuteReader()) { // 数据处理操作 while (reader.Read()) { // 处理逻辑 } } cmd.Parameters.Clear(); } }
using语句是C#中管理非托管资源的最佳实践,能避免手动调用Dispose()时可能出现的遗漏。
5. 验证修复效果
修改完存储过程和客户端代码后,再次执行两次GetData():
- 如果不再触发ORA-01001,说明问题解决
- 再查询
V$OPEN_CURSORS,游标数量应该不会持续增长
内容的提问来源于stack exchange,提问作者Gadziu

