TSQL: транзакция Try-Catch в триггерах

Я пытаюсь поставить инструкцию try-catch внутри триггера с помощью Microsoft Server 2005.

BEGIN TRANSACTION
BEGIN TRY
    --Some More SQL
    COMMIT TRANSACTION
END TRY
BEGIN CATCH
    IF (XACT_STATE()) = -1
    BEGIN
        ROLLBACK TRANSACTION;
    END;
END CATCH

Проблема заключается в том, что я не хочу, чтобы триггер терпел неудачу, если что-то попало в блок try-catch. На данный момент я получаю сообщение об ошибке "Транзакция завершилась триггером. Партия была прервана". если транзакция не удалась. Как я могу заставить триггер терпеть неудачу изящно?


Кроме того, если я удалю транзакцию, я получаю сообщение об ошибке "Транзакция, обреченная на триггер. Пакет был прерван".

BEGIN TRY
    --Some More SQL
END TRY
BEGIN CATCH
    return
END CATCH

Есть ли способ обойти это?

Ответ 1

По моему опыту любая ошибка, попавшая в попытку catch в триггере, откат всей транзакции; вы можете использовать транзакцию сохранения. Я думаю, вам нужно посмотреть, что происходит в "Some more sql", и определить, можете ли вы писать аргументы case/if вокруг него, чтобы остановить ошибку.

В зависимости от того, что вы делаете, вы можете использовать сохранить транзакцию и зафиксировать это в catch

В вашем коде что-то вроде этого

SAVE TRANSACTION BeforeUpdate;
BEGIN TRY
        --Some More SQL
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION BeforeUpdate;
        return
END CATCH

Ответ 2

Не откат в триггере, и нет необходимости запускать транзакцию.

ROLLBACK TRANSACTION откатит исходный триггер DML и дополнительную транзакцию триггера. Таким образом, пакет будет прерван

Edit:

Я предлагаю не иметь "RETURN" в вашем блоке catch и просто позволить коду завершить Я никогда не игнорировал захваченную ошибку в триггере (но я использую TRY/CATCH в триггерах с откатом и raiserror для повторного броска), поэтому это предположение, но возврат, вероятно, является ненормальным условием выхода в триггере

Кроме того, сначала попробуйте избежать условия ошибки. Измените --some more sql, чтобы избежать ошибки. Например, добавьте if exists(... для проверки дубликата первой или аналогичной

Ответ 3

Чтобы не потерять транзакционные данные перед действием триггера, вам нужно позвонить COMMIT TRAN. Сделайте это до блока TRY/CATCH, и вы получите желаемые результаты.

Пример:

COMMIT TRAN
BEGIN TRY
    -- possible error occurs here...
END TRY
BEGIN CATCH
    PRINT 'Error on line ' + CAST(ERROR_LINE() AS VARCHAR(10))
    PRINT ERROR_MESSAGE()
END CATCH

Он все равно оставит следующую ошибку: не уверен, как этого избежать:

The transaction ended in the trigger. The batch has been aborted.

Но как исходная транзакция, так и транзакция триггера должны успешно совершить.

UPDATE: Чтобы избежать последней ошибки исключения, вызовите BEGIN TRAN в инструкции TRY. Примечание. Корпорация Майкрософт рекомендует НЕ называть COMMIT TRAN внутри триггера, но если это неизбежно, это должно сработать для вас.

Пример:

COMMIT TRAN
BEGIN TRY
    BEGIN TRAN

Ответ 5

Не лучший способ, но он работает. Запустите новую транзакцию и выполните обычный откат транзакции и начните новую транзакцию в конце для неявной фиксации транзакции

http://msdn.microsoft.com/en-us/library/ms187844(v=SQL.90).aspx

Ответ 6

Эта демонстрация достигает многих из вещей, о которых говорилось выше. Сообщения об ошибках становятся необязательными. Трюк, который заставляет его работать, находится в вложенном динамическом выполнении.

    if object_id('toto')  is not  null drop table toto
    go
    create table toto (i int);
    go
    if object_id('toto2')  is not  null drop table toto2
    go
    create table toto2 (i int);
    go
    create Trigger trtoto
    ON toto
    Instead Of Insert
    as
    Begin
      BEGIN TRY
        set nocount  on
        insert into  toto  values(2)

        declare @sql nvarchar(max) =  'insert into toto2 values(3); select * from ThisTableDoesntexist'

        Exec sp_executeSql N'set xact_abort off; exec (@sql) ', N'@sql nvarchar(max)', @sql

      END TRY

      BEGIN CATCH
        PRINT  'Error on line ' + CAST(ERROR_LINE() AS VARCHAR(10))
        PRINT ERROR_MESSAGE()
      END CATCH
    End

GO
-- tests
set nocount on
insert into toto values (1)  -- is not inserted on purpose by  the trigger
select * from toto   -- other value inserted despite the error
select * from toto2  -- other value inserted in other table despite the error

Ответ 7

Используйте SET XACT_ABORT OFF. Когда оператор Transact-SQL встречает ошибку, он просто поднял сообщение об ошибке и транзакция продолжает обработку. Следующий код предназначен для создания триггера:

Create TRIGGER [dbo].tr_Ins_Table_Master ON [dbo].Table_Master
 AFTER INSERT
AS
BEGIN
set xact_abort off
BEGIN TRY
        --your SQL          
        INSERT INTO Table_Detail
        SELECT MasterID,Name FROM INSERTED

END TRY

BEGIN CATCH     
    select ERROR_MESSAGE()
END CATCH

END

Ответ 8

Возможно, вам будет полезно узнать, что вы пытаетесь сделать в триггере.

Триггер - это часть транзакции, которая отправляет данные во вставленные или удаленные таблицы. Если это не удастся, оно откатит всю транзакцию. Если вы ожидаете, что триггер будет терпеть неудачу изредка, но не откат команды, которая вызвала срабатывание триггера, возможно, вам нужно переосмыслить, правильно ли использовать триггер.

Ответ 9

Вы можете установить XACT_Abort в положение OFF в начале запуска.