7

昨天我在我们的生产过程中处理了一个奇怪的错误。语句执行失败

if v_cursor%isopen then
  close v_cursor; -- here was an error 
end if;

经过一番深入研究后,我发现问题出在打开此光标的子程序中。我通过在子程序中添加输出参数 sys_refcursor 来修复错误。为了澄清情况,请考虑以下测试代码:

procedure nested_test(test  number,
                        p_cur out sys_refcursor)
  is  
    procedure nested_procedure_fail is
    begin      
      open p_cur for
        select 1, 2, 3, 4
          from dual
         where 1 = 0;
    end;

    procedure nested_procedure_success(p_cur out sys_refcursor) is
    begin
      open p_cur for
        select 1, 2, 3, 4
          from dual
         where 1 = 0;
    end;

  begin
    if test = 1 then
      nested_procedure_fail;
    else
      if test = 2 then
        nested_procedure_success(p_cur => p_cur);
      else
        open p_cur for
          select 6, 7, 8, 9
            from dual
           where 1 = 1;
      end if;
    end if;
  end;

  procedure test_fail is
    v_cur sys_refcursor;
  begin
    nested_test(test => 1, p_cur => v_cur);
    if v_cur%isopen then
      close v_cur;
    end if;
  end;

  procedure test_success is
    v_cur sys_refcursor;
  begin
    nested_test(test => 2, p_cur => v_cur);
    if v_cur%isopen then
      close v_cur;
    end if;
  end;

如果我尝试运行test_success一切正常,但test_fail我收到一条消息

ORA-01001: 游标无效

我找不到有关此的任何信息。谁能解释为什么这段代码会失败?

甲骨文版本:

Oracle Database 11g Enterprise Edition Release 11.2.0.3.0 - 64bit Production
PL/SQL Release 11.2.0.3.0 - Production
CORE    11.2.0.3.0  Production
TNS for Solaris: Version 11.2.0.3.0 - Production
NLSRTL Version 11.2.0.3.0 - Production
4

2 回答 2

13

这似乎是错误 7174888,或者至少与它密切相关。对此的描述是“当 sys_refcursor 传递给另一个过程时引发 ORA-6504”,但如果我更改test_fail为进行提取,我也可以做到这一点:

  procedure test_fail is
    v_cur sys_refcursor;
    a number;
    b number;
    c number;
    d number;
  begin
    nested_test(test => 1, p_cur => v_cur);
    if v_cur%isopen then
      fetch v_cur into a,b,c,d;
      close v_cur;
    end if;
  end;

我明白了ORA-06504: PL/SQL: Return types of Result Set variables or query do not match

错误报告中的解决方法可以解决 fetch 和 close 问题。

将 ref 游标初始化为将被访问的最高级别的非 NULL 值

  begin
    /* Dummy open to avoid bug 7174888 */
    open v_cur for 'select 1 from dual';
    nested_test(test => 1, p_cur => v_cur);
    if v_cur%isopen then
      fetch v_cur into a,b,c,d;
      close v_cur;
    end if;
  end;
于 2012-07-05T11:12:57.573 回答
2

一个有趣的问题!只是想补充一些东西。

对我来说,真正的问题在于依赖 IS_OPEN 来确定游标是否有效。Oracle 可能出于多种原因抛出 INVALID_CURSOR,并且可能有一个无效的“打开”游标。假设打开的游标必须有效似乎是合理的(因此我们可以从中获取或执行其他操作,例如简单的关闭),但情况不一定如此。

例如,您不能在远程过程调用中使用游标变量(通过 dblinks)。如果在 1 个数据库实例上调用 open 并在另一个实例上调用 fetch(如果在 db_A 上定义任何版本的nested_test,然后从 db_B 调用),同样的示例,即使使用 Alex 的解决方法,也会失败。然而,对 ISOPEN 的测试仍然会返回 TRUE,但是尝试使用游标(获取)会失败。

INVALID_CURSOR 可能因其他原因引发(例如超出最大打开游标,或者有时打开游标并等待一段时间再尝试使用它)。

综上所述,我所知道的没有“ISVALID”测试。imo 最好的方法是在同一个程序或子程序中打开、获取和关闭游标。创建一个只负责打开一个游标的过程对我来说有点奇怪(但我确信这是有原因的),并且可能会导致难以解释的问题(比如这个)。如果您必须让另一个程序为您打开游标,那么您可能希望将获取并最终关闭游标的代码包含在匿名块中并捕获 INVALID_CURSOR 异常。

只是我的胡言乱语;-)

于 2012-07-05T16:42:18.250 回答