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

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


person Eldila    schedule 19.05.2009    source источник
comment
Я пытаюсь обновить устаревшую базу данных всякий раз, когда что-либо вставляется в таблицу. Проблема в том, что я не хочу, чтобы вставка потерпела неудачу, если триггер не работает. Устаревшая база данных — не самая надежная система.   -  person Eldila    schedule 20.05.2009
comment
Я собираюсь начать использовать слово обреченный в большем количестве сообщений об ошибках.   -  person AaronLS    schedule 22.12.2010


Ответы (9)


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

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

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

SAVE TRANSACTION BeforeUpdate;
BEGIN TRY
        --Some More SQL
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION BeforeUpdate;
        return
END CATCH
person u07ch    schedule 19.05.2009
comment
Я не знал о СОХРАНЕНИИ ТРАНЗАКЦИИ +1 - person ichiban; 19.05.2009
comment
Это было там некоторое время :), но это влияет на производительность, поэтому используйте его экономно. - person u07ch; 19.05.2009
comment
Означает ли это, что у нас должна быть открытая транзакция? Можем ли мы открыть его в начале триггера? - person meir; 02.10.2012
comment
Я тоже хотел бы знать ответ на вопрос @meir. Я увидел сообщение об этой транзакции обречено в триггере после того, что, насколько я могу судить, было операцией, которая даже не включала транзакцию. - person Matt; 09.12.2013
comment
@meir Триггеры всегда имеют транзакцию. Если он еще не существует при запуске триггера, он будет создан для него. - person RBarryYoung; 25.04.2014
comment
для тех, кто приходит сюда в поисках ответа, вот пара ссылок, в которых немного подробно описан процесс сохранения транзакции. stackoverflow.com/a/7490088/6916098 | blackwasp.co.uk/SQLSavepoints.aspx - person blogs4t; 03.09.2017

Не откатывайтесь в триггере и не нужно запускать транзакцию.

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

Редактировать:

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

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

person gbn    schedule 19.05.2009
comment
Это действительно полезно. Однако я только что столкнулся с другой проблемой. Подробности смотрите в обновленном вопросе. - person Eldila; 19.05.2009
comment
К сожалению, без ROLLBACK в триггере, что очень беспорядочно и может полностью испортить поддержку транзакций .NET! -- Я не знаю способа полностью установить ограничения с помощью триггеров. RAISERROR(...,16,1) достаточно, чтобы во многих случаях .NET заметил и выдал исключение, но транзакции автоматической фиксации (IMPLICIT_TRANSACTIONS = OFF) не затрагиваются RAISERROR и, таким образом, обходят любые ограничения, которые этот триггер пытался добавить. Единственное решение, которое я нашел, состоит в том, чтобы не использовать автофиксацию транзакций и явно использовать ROLLBACK/COMMIT с TRY/CATCH. Худший. Дизайн. Всегда. - person ; 10.03.2012
comment
(Конечно, у Sybase есть вполне жизнеспособный ROLLBACK TRIGGER :-/) - person ; 10.03.2012

Используйте 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
person 神來之筆    schedule 20.04.2017

Чтобы избежать потери транзакционных данных до действия триггера, вам нужно вызвать 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.

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

ОБНОВЛЕНИЕ: Чтобы избежать последней ошибки исключения, вызовите BEGIN TRAN в инструкции TRY. Обратите внимание: Microsoft рекомендует НЕ вызывать COMMIT TRAN в триггере, но если это неизбежно, это должно сработать для вас.

Пример:

COMMIT TRAN
BEGIN TRY
    BEGIN TRAN
person rjchicago    schedule 02.12.2011

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

    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
person Maurice Pelchat    schedule 12.10.2015
comment
Это почти 2021 год .... написал хранимую процедуру для выполнения http-запроса к картам Google ... хотел использовать ее за триггером для геокодирования ... это был единственный способ не беспокоить транзакцию, которая инкапсулировала триггер ... молодец, мистер Пелчат... теперь мне не нужно думать об асинхронных триггерах и тому подобном... - person nenea; 05.12.2020

u07ch,

К сожалению, вы не можете использовать save transaction и try.. catch вместе — они просто не могут работать вместе:

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

person A-K    schedule 19.05.2009
comment
Это можно сделать; вот пример от Microsoft. Это просто ПИТА. Вам нужно знать, создали ли вы транзакцию (и как), а затем вам нужно посмотреть XACT_STATE при очистке... и это даже не в контексте триггера, что немного уродливо полностью. Я ненавижу транзакции SQL Server и обработку исключений. - person ; 10.03.2012
comment
@pst иногда это невозможно сделать: если ваша транзакция обречена, вы не можете вернуться к своей точке сохранения. Все, что вы можете сделать, это откатить всю транзакцию. - person A-K; 11.03.2012
comment
Естественно... пример охватывает и этот случай. - person ; 11.03.2012

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

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

person HLGEM    schedule 19.05.2009
comment
Что тогда использовать вместо этого? - person Joel; 16.08.2016

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

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

person Khawaja    schedule 07.12.2011

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

person Jami    schedule 22.07.2015