TSQL: транзакция Try-Catch в триггере - PullRequest
14 голосов
/ 19 мая 2009

Я пытаюсь поместить оператор 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

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

Ответы [ 9 ]

8 голосов
/ 19 мая 2009

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

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

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

SAVE TRANSACTION BeforeUpdate;
BEGIN TRY
        --Some More SQL
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION BeforeUpdate;
        return
END CATCH
7 голосов
/ 19 мая 2009

Не выполняйте откат в триггере, и нет необходимости начинать транзакцию.

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

Edit:

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

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

3 голосов
/ 20 апреля 2017

Использовать 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
2 голосов
/ 02 декабря 2011

Чтобы не потерять транзакционные данные перед действием триггера, вам нужно вызвать 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. Обратите внимание, что Microsoft рекомендует НЕ вызывать COMMIT TRAN внутри триггера, но если это неизбежно, это должно работать для вас.

Пример:

COMMIT TRAN
BEGIN TRY
    BEGIN TRAN
1 голос
/ 12 октября 2015

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

    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
1 голос
/ 07 декабря 2011

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

.

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

1 голос
/ 20 мая 2009

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

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

1 голос
/ 19 мая 2009

u07ch,

К сожалению, вы не можете использовать сохранение транзакции и попробовать .. поймать вместе - они просто не могут работать вместе:

http://sqlblog.com/blogs/alexander_kuznetsov/archive/2008/11/15/avoid-mixing-old-and-new-styles-of-error-handling.aspx

0 голосов
/ 22 июля 2015

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...