为啥它在没有 PRAGMA AUTONOMOUS_TRANSACTION 的情况下工作

Posted

技术标签:

【中文标题】为啥它在没有 PRAGMA AUTONOMOUS_TRANSACTION 的情况下工作【英文标题】:why it is working without PRAGMA AUTONOMOUS_TRANSACTION为什么它在没有 PRAGMA AUTONOMOUS_TRANSACTION 的情况下工作 【发布时间】:2019-04-24 06:44:55 【问题描述】:

我对

有误解

PRAGMA AUTONOMOUS_TRANSACTION

指令。

据我所知,它用于记录或审计程序,由主程序(自治、程序、函数或触发器)独立运行。

我对生成 DUP_VAL_ON_INDEX 的表进行了更新。在这个异常中,我调用了一个日志记录过程,它将错误记录到一个表中。在日志记录过程中,我没有指定 PRAGMA AUTONOMOUS_TRANSACTION 指令,但它仍然会在我的日志记录表中插入。

这是我的代码:

create table TEST_PRAGMA
    ( COL_1 number primary key
    , COL_2 number
    );

--
insert into TEST_PRAGMA values (1, 200);
insert into TEST_PRAGMA values (2, 200);
--
create table T_LOG    
    ( msg_num number primary key
    , MSG_DATE timestamp(6)
    , INFO_MSG varchar2(10)
    , LONG_MSG varchar2(100)
    );
--    
create sequence SEQ_TEST start with 1 increment by 1 nocache nocycle;

包装:

create or replace package pkg_logging as

    procedure PRC_LOG ( P_MSG_NUM number 
                      , P_MSG_DATE timestamp
                      , P_INFO_MSG varchar2
                      , p_long_msg varcahr2);
end PKG_LOGGING;
--
create or replace package body pkg_logging as

    procedure PRC_LOG ( P_MSG_NUM number 
                      , P_MSG_DATE timestamp
                      , P_INFO_MSG varchar2
                      , P_LONG_MSG VARCHAR2)
                      as
    begin

        insert into T_LOG
            ( MSG_NUM
            , MSG_DATE
            , INFO_MSG
            , LONG_MSG
            )
        values
            ( P_MSG_NUM 
            , P_MSG_DATE
            , P_INFO_MSG
            , P_LONG_MSG

            );
        commit;
    EXCEPTION
        when OTHERS then
            rollback;
            RAISE_APPLICATION_ERROR(-20000, 'other error has occured: ' || sqlcode || ' - ' || sqlerrm);
    end PRC_LOG;
end PKG_LOGGING;
--
set SERVEROUTPUT on;
begin

    update TEST_PRAGMA set COL_1 = 1 where COL_2 = 200;
    commit;

EXCEPTION
    when DUP_VAL_ON_INDEX then 
    dbms_output.put_line ('DUP_VAL_ON_INDEX error has occured');
        PKG_LOGGING.PRC_LOG(SEQ_TEST.NEXTVAL, systimestamp, 'error', 'test de logging');
        rollback;
end;

因为我没有指定 PRAGMA 指令,所以我希望即使逻辑正确也不会记录错误。

谁能解释一下为什么它仍然记录我的错误并提供一个示例,如果我没有指定 PRAGMA AUTONOMOUS_TRANSACTION 指令,它不会记录代码?

谢谢,

【问题讨论】:

在您的程序中,您使用 COMMIT 进行 INSERT - 除了要插入的行之外,您还期望什么? 从您的过程中删除 EXCEPTION when OTHERS then 部分。用一般的 -20000 错误覆盖特定错误没有任何意义。跳过它,错误信息将几乎相同,并且无论如何都会回滚。 只是添加到 Wernfried 的第一条评论:您不需要 AT pragma 来提交对数据库的更改。您需要它在会话中提交一些更改而不提交其他更改。在您的情况下,更新失败,然后您进行了插入,然后您提交了。所以日志行被保存了。 【参考方案1】:

谁能解释我为什么它仍然记录我的错误并提供一个 如果我没有指定 PRAGMA,它不会记录代码的示例 AUTONOMOUS_TRANSACTION 指令好吗?

错误是InsertedLog 表中,因为您将其作为Exception handling 处理。您需要将AUTONOMOUS 事务的行为理解为Independent 一段代码,即使主调用proc/pkg 失败,它也会执行。它没有作为Exception Handling 的一部分处理。如下演示所示,您可以看到标记为AUTONOMOUS 的proc 在BEGIN 块中直接调用,而不是在Exception 块中调用以了解行为。

DECLARE
    l_salary   NUMBER;
--Private Proc marking as Autonomous transaction 
procedure nested_block ‬ 
   as 
   pragma AUTONOMOUS_TRANSACTION;
   BEGIN
     UPDATE emp
     SET salary=salary+15000
     WHERE emp_no=1002;
    COMMIT;
    END;‭
--Main Block    ‬
BEGIN
SELECT salary 
INTO l_salary 
FROM emp 
WHERE emp_no=1001; 

Dbms_output.put_line('Before Salary of 1001 is'||l_salary); 

SELECT salary 
INTO l_salary 
FROM emp WHERE emp_no=1002;

Dbms_output.put_line('Before Salary of 1002 is '|| 1_salary);

UPDATE emp
    SET
        salary = salary + 5000
WHERE emp_no = 1001;

--Calling Autonomous transaction
nested_block;

--And rolling back previous updates.
ROLLBACK;

SELECT salary INTO
    l_salary
FROM emp
WHERE emp_no = 1001;

dbms_output.put_line('After Salary of 1001 is'|| l_salary);

SELECT salary 
INTO  l_salary
FROM emp
WHERE emp_no = 1002;

dbms_output.put_line('After Salary of 1002 is ' || l_salary);

end;

输出:

输出将在Autonomous 事务中完成Update。在main 块中完成的更新将是rolledback,但不是在标记为Autonomousprivate proc 中完成的更新

Before Salary of 1001 is 15000 
Before Salary of 1002 is 10000 
After  Salary of 1001 is 15000 
After  Salary of 1002 is 25000 

【讨论】:

【参考方案2】:

PKG_LOGGING.PRC_LOG() 有一个提交语句,所以它会提交。

假设您的代码看起来像这样:

set SERVEROUTPUT on;
begin

    insert into TEST_PRAGMA values (3, 300);
    PKG_LOGGING.PRC_LOG(SEQ_TEST.NEXTVAL, systimestamp, 'info', 'inserted a record');

    update TEST_PRAGMA set COL_1 = 1 where COL_2 = 200;
    commit;

EXCEPTION
    when DUP_VAL_ON_INDEX then 
    dbms_output.put_line ('DUP_VAL_ON_INDEX error has occured');
        PKG_LOGGING.PRC_LOG(SEQ_TEST.NEXTVAL, systimestamp, 'error', 'test de logging');
        rollback;
end;

您将在 TEST_PRAGMA 中有多少条记录? 三个。因为在我们调用 PKG_LOGGING.PRC_LOG() 时插入已提交,因此异常处理程序中的回滚无效。这就是我们应该在审计和日志记录例程中使用PRAGMA AUTONOMOUS_TRANSACTION 的原因:这样我们就可以成功地保存我们的日志消息,而不会影响更广泛的事务。

所以你应该将PRAGMA AUTONOMOUS_TRANSACTION 添加到PKG_LOGGING.PRC_LOG()。


顺便说一句,我认为您应该小心在日志包中使用这样的错误处理程序:

EXCEPTION
    when OTHERS then
        rollback;
        RAISE_APPLICATION_ERROR(-20000, 'other error has occured: ' || sqlcode || ' - ' || sqlerrm);
end PRC_LOG;

在某些情况下,如果我们无法记录重要信息,我们肯定会想要停止我们的进程。但其他时候,我们希望日志记录能够优雅地失败。例如,如果它不能记录错误,我需要夜间批处理运行异常终止,因为该日志是我知道什么(如果有的话)出了什么问题的唯一方法,最好不要让整个事情运行它来运行不完全,我不知道有些事情失败了。但是,如果我只是在 Test 中编写一些跟踪消息,我可能更喜欢在没有完整跟踪集的情况下结束长时间运行的过程,而不是因为日志记录表空间不足而异常终止。

另外,不需要使用raise_application_error()。只需在回滚后发出raise; 并完成它。

【讨论】:

以上是关于为啥它在没有 PRAGMA AUTONOMOUS_TRANSACTION 的情况下工作的主要内容,如果未能解决你的问题,请参考以下文章

oracle 触发器 pragma autonomous_transaction

PRAGMA AUTONOMOUS_TRANSACTION

AUTONOMOUS_TRANSACTION

#pragma 标记的意义​​是啥?为啥我们需要#pragma 标记?

如果行程计数不恒定,为啥我的#pragma-unrolled 循环的性能会下降?

如何实现为 _Pragma 创建带引号的字符串的宏?