SQL Server XACT_ABORT с исключением
У меня есть большая хранимая процедура, которая использует несколько блоков TRY/CATCH, чтобы отлавливать и регистрировать отдельные ошибки. Я также обернул транзакцию вокруг всего содержимого процедуры, чтобы иметь возможность откатить всю вещь в случае ошибки, возникшей где-то по пути (чтобы предотвратить много грязной очистки); XACT_ABORT был включен, поскольку в противном случае он не откатил бы всю транзакцию.
Ключевой компонент:
В моей базе данных есть таблица, в которую вставляется запись при каждом запуске этой процедуры с результатами операций и подробностями о том, что пошло не так.
Происходит забавная вещь - на самом деле, когда я наконец выяснил, что было не так, это было довольно очевидно... оператор вставки в мою таблицу журналов также откатывается, следовательно, если я не запускаю это из SSMS, Я не смогу увидеть, что это даже было выполнено, так как откат удаляет все трансы активности.
Вопрос:
Можно ли было бы откатить всю транзакцию за исключением этого единственного оператора вставки? Я все еще хотел бы сохранить сообщение об ошибке, которое я компилирую во время выполнения хранимой процедуры.
Спасибо!
~ Eli
Обновление 6/28
Вот пример кода того, на что я смотрю. Ключевое различие между этим и примерами, представленными @Alex и @gameiswar, состоит в том, что в моем случае блоки try/catch все вложены в одну транзакцию. Цель этого состоит в том, чтобы иметь несколько уловов (для нескольких таблиц), хотя мы бы откатили весь беспорядок, даже если последнее обновление не удалось.
SET XACT_ABORT ON;
BEGIN TRANSACTION
DECLARE @message AS VARCHAR(MAX) = '';
-- TABLE 1
BEGIN TRY
UPDATE TABLE xx
SET yy = zz
END TRY
BEGIN CATCH
SET @message = 'TABLE 1 '+ ERROR_MESSAGE();
INSERT INTO LOGTABLE
SELECT
GETDATE(),
@message
RETURN;
END CATCH
-- TABLE 2
BEGIN TRY
UPDATE TABLE sss
SET tt = xyz
END TRY
BEGIN CATCH
SET @message = 'TABLE 2 '+ ERROR_MESSAGE();
INSERT INTO LOGTABLE
SELECT
GETDATE(),
@message
RETURN;
END CATCH
COMMIT TRANSACTION
3 ответа
Хорошо... Я смог решить эту проблему, используя комбинацию замечательных предложений, выдвинутых Алексом и GameisWar, с добавлением оператора потока управления T-SQL GOTO.
Основная идея заключалась в том, чтобы сохранить сообщение об ошибке в переменной, которая переживет откат, а затем Catch отправит вас на метку FAILURE, которая будет выполнять следующие действия:
- Откат транзакции
- Вставьте запись в таблицу журнала, используя данные из вышеупомянутой переменной
- Выход из хранимой процедуры
Я также использую второй оператор GOTO, чтобы убедиться, что при успешном запуске будет пропущен раздел FAILURE и зафиксирована транзакция.
Ниже приведен фрагмент кода того, как выглядел тестовый SQL. Это сработало как прелесть, и я уже реализовал это и протестировал (успешно) в нашей производственной среде.
Я действительно ценю всю помощь и вклад!
SET XACT_ABORT ON
DECLARE @MESSAGE VARCHAR(MAX) = '';
BEGIN TRANSACTION
BEGIN TRY
INSERT INTO TEST_TABLE VALUES ('TEST'); -- WORKS FINE
END TRY
BEGIN CATCH
SET @MESSAGE = 'ERROR - SECTION 1: ' + ERROR_MESSAGE();
GOTO FAILURE;
END CATCH
BEGIN TRY
INSERT INTO TEST_TABLE VALUES ('TEST2'); --WORKS FINE
INSERT INTO TEST_TABLE VALUES ('ANOTHER TEST'); -- ERRORS OUT, DATA WOULD BE TRUNCATED
END TRY
BEGIN CATCH
SET @MESSAGE = 'ERROR - SECTION 2: ' + ERROR_MESSAGE();
GOTO FAILURE;
END CATCH
GOTO SUCCESS;
FAILURE:
ROLLBACK
INSERT INTO LOGG SELECT @MESSAGE
RETURN;
SUCCESS:
COMMIT TRANSACTION
Вы можете попробовать что-то вроде ниже, что гарантирует, что вы регистрируете операцию. Это использует тот факт, что переменные таблицы не откатываются.
Код Psuedo только для того, чтобы дать вам представление:
create table test1
(
id int primary key
)
create table logg
(
errmsg varchar(max)
)
declare @errmsg varchar(max)
set xact_abort on
begin try
begin tran
insert into test1
select 1
insert into test1
select 1
commit
end try
begin catch
set @errmsg=ERROR_MESSAGE()
select @errmsg as "in block"
if @@trancount>0
rollback tran
end catch
set xact_abort off
select @errmsg as "after block";
insert into logg
select @errmsg
select * from logg
Я не знаю деталей, но ИМХО общая логика может быть такой.
--set XACT_ABORT ON --not include it
declare @result varchar(max) --collect details in case you need it
begin transaction
begin try
--your logic here
--if something wrong RAISERROR(...@result)
--everything OK
commit
end try
begin catch
--collect error_message() and other into @result
rollback
end catch
insert log(result) values (@result)