关于pl / sql异常的问题

时间:2009-11-18 08:33:08

标签: oracle exception transactions plsql

以下文字摘自oracle文档 Oracle®数据库PL / SQL语言参考11g第1版(11.1)

  

未处理的异常也会影响   子程序。如果退出子程序   成功,PL / SQL为其分配值   OUT参数。但是,如果你退出   带有未处理的异常,PL / SQL   不为OUT分配值   参数(除非它们是NOCOPY   参数)。 另外,如果存储了   子程序失败,未处理   异常,PL / SQL不回滚   子程序完成的数据库工作。

注意粗体文字,这是真的吗?我很好奇,所以我写了下面的例子来测试它。

-- create a test table
CREATE TABLE e AS SELECT * FROM HR.EMPLOYEES;   

-- create p1 which will update a row in table e
CREATE OR REPLACE PROCEDURE p1
IS
    ex EXCEPTION;
    row e%ROWTYPE;
BEGIN
    select * into row from e where employee_id=100;

    row.employee_id := 100;
    row.first_name := 'yang';

    -- update
    UPDATE e set ROW = row where employee_id = 100;
    -- and raise an error
    RAISE ex;
END;


BEGIN
    -- call the upper procedure
    p1;
END;

-- test whether update success
select * from e where employee_id=100;

-- the upper query gives me
Steven

所以我的问题是:我是对的吗?

2 个答案:

答案 0 :(得分:9)

在SO上查看此问题:Does Oracle roll back the transaction on an error?

在您的情况下,过程P1将成功或失败并回滚其更改。为什么它看起来像文档中的陈述相反(p1在程序中间失败并且未完成的工作)?

答案在于sentence just before your quote

  

请记住,如果找不到引发异常的处理程序,PL / SQL会向主机环境返回未处理的异常错误,从而确定结果。例如,在Oracle预编译器环境中,将回滚由失败的SQL语句或PL / SQL块所做的任何数据库更改。

这意味着当程序失败时,如果未处理引发的异常,则将回滚不完整的工作。但是,如果捕获了异常而没有重新引发异常,则不完整的工作将保持不变。

我们可以通过在您的示例中添加一个WHEN OTHERS块(而不是重新引发异常 - 当然这是非常糟糕的主意,请参阅下面的原因)来显示此行为:

SQL> BEGIN
  2     -- call the upper procedure
  3     p1;
  4  EXCEPTION
  5     WHEN OTHERS THEN
  6        dbms_output.put_line('log error...');
  7  END;
  8  /

log error...

PL/SQL procedure successfully completed

SQL> select employee_id, first_name from e where employee_id = 100;

EMPLOYEE_ID FIRST_NAME
----------- --------------------
        100 yang

你真的 never want to do this :我们留下了未完成的工作,记录了错误,并且没有重新提出它我们有一个潜在的严重错误。此外,默默地忽略异常是灾难的一种方法。

答案 1 :(得分:2)

“此外,如果存储的子程序因未处理的异常而失败,PL / SQL不会回滚子程序完成的数据库工作。”

以上引用具体是指存储的子程序,但下面的代码块是匿名块,而不是存储的子程序

BEGIN
    -- call the upper procedure
    p1;
END;

因此,报价不适用。顶级匿名块的失败是执行回滚的失败(就像任何其他SQL语句一样) 使用以下代码进行测试表明,在触发SERVERERROR触发器时(即返回主机之前),值1和10的INSERT已经回滚(因为1的重新插入不会失败)重复密钥或死锁)。

drop table test_se_auto_tbl;

create table test_se_auto_tbl (id number(2) primary key, val varchar2(20));

create or replace trigger test_se_auto_trg after servererror on schema 
begin
  for c_rec in (select id, val from test_se_auto_tbl) loop
dbms_output.put_line(c_rec.id||':'||c_rec.val);
  end loop;
  dbms_output.put_line('Trigger');
  insert into test_se_auto_tbl values (1,'test ');
end;
/

begin
  insert into test_se_auto_tbl values (1,'test ');
  insert into test_se_auto_tbl values (10,'test 10');
  insert into test_se_auto_tbl values (100,'test 100');
end;
/

select id, val from test_se_auto_tbl;

支持这一假设的另一种情况。在这种情况下,匿名PL / SQL块在嵌套在另一个PL / SQL块内的EXECUTE IMMEDIATE中调用。虽然外部块捕获了异常,但是当EXECUTE IMMEDIATE运行原子语句时,插入已经回滚。

DECLARE
  v_num NUMBER;
begin
   begin
     execute immediate 
         'declare 
           v_num number(2); 
         begin 
           insert into dummy values (1);
           dbms_output.put_line(101);
           v_num := 100;
         end;';
   exception
      when others then null;
   end;
   select count(*) into v_num from dummy;
   dbms_output.put_line(v_num);
end;
/