#oracle #exception #plsql #transactions #pragma
#Oracle #исключение #plsql #транзакции #pragma
Вопрос:
У меня есть недопонимание относительно
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, пожалуйста?
Спасибо,
Комментарии:
1. В вашей процедуре вы выполняете вставку с ФИКСАЦИЕЙ — чего еще вы ожидаете, кроме строки, которая будет вставлена?
2. Удалите
EXCEPTION when OTHERS then
часть из вашей процедуры. Нет никакого смысла перезаписывать конкретную ошибку общей ошибкой -20000. Просто пропустите это, сообщения об ошибках будут почти одинаковыми, и откат будет выполнен в любом случае.3. Просто чтобы добавить к первому комментарию Вернфрида: вам не нужна прагма AT для фиксации изменений в БД. Вам это нужно, чтобы зафиксировать НЕКОТОРЫЕ изменения в вашем сеансе, не фиксируя другие. В вашем случае произошел сбой обновления, затем вы выполнили вставку, затем вы зафиксировали. Итак, строка журнала была сохранена.
Ответ №1:
Кто-нибудь может объяснить мне, почему он все еще регистрирует мою ошибку и предоставить пример, в котором он не регистрирует код, если я не укажу директиву PRAGMA AUTONOMOUS_TRANSACTION, пожалуйста?
Ошибка находится Inserted
в Log
таблице, поскольку вы обрабатываете ее как Exception handling
. Вам нужно понимать поведение AUTONOMOUS
транзакции как Independent
фрагмента кода, который выполняется, даже если основной вызов proc/pkg
завершается неудачей. Он не обрабатывается как часть Exception Handling
. Как показано в приведенной ниже демонстрации, вы можете видеть, что процедура, помеченная как AUTONOMOUS
вызываемая в 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;
Вывод:
Вывод будет Update
выполнен в Autonomous
транзакции. Обновления, выполняемые в main
блоке, будут rolledback
, но не те, которые выполняются в private proc
, помеченные как Autonomous
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;
В некоторых ситуациях мы определенно хотели бы остановить наш процесс, если мы не можем зарегистрировать важную информацию. Но в других случаях мы хотим, чтобы ведение журнала завершалось корректно. Например, мне нужно, чтобы ночной пакетный запуск был отменен, если он не может записывать ошибки, потому что этот журнал — мой единственный способ узнать, что — если что-то пошло не так, и лучше, чтобы все это не запускалось, чтобы оно выполнялось неполно, и я не знал, что что-то не удалось. Но если я просто пишу несколько сообщений трассировки в тесте, я мог бы предпочесть, чтобы длительный процесс завершался без полного набора трассировок, а не прерывался, потому что в таблице ведения журнала не хватает места.
Кроме того, использование raise_application_error()
не обязательно. Просто выполните проблему raise;
после отката и покончите с этим.