Sqlserver
 sql >> Base de Dados >  >> RDS >> Sqlserver

Erro de gatilho:a transação atual não pode ser confirmada e não pode suportar operações que gravam no arquivo de log


Este erro ocorre quando você usa um bloco try/catch dentro de uma transação. Vamos considerar um exemplo trivial:
SET XACT_ABORT ON

IF object_id('tempdb..#t') IS NOT NULL
    DROP TABLE #t
CREATE TABLE #t (i INT NOT NULL PRIMARY KEY)

BEGIN TRAN
    INSERT INTO #t (i) VALUES (1)
    INSERT INTO #t (i) VALUES (2)
    INSERT INTO #t (i) VALUES (3)
    INSERT INTO #t (i) VALUES (1) -- dup key error, XACT_ABORT kills the batch
    INSERT INTO #t (i) VALUES (4) 

COMMIT  TRAN
SELECT * FROM #t

Quando a quarta inserção causa um erro, o lote é encerrado e a transação é revertida. Sem surpresas até agora.

Agora vamos tentar lidar com esse erro com um bloco TRY/CATCH:
SET XACT_ABORT ON
IF object_id('tempdb..#t') IS NOT NULL
    DROP TABLE #t
CREATE TABLE #t (i INT NOT NULL PRIMARY KEY)

BEGIN TRAN
    INSERT INTO #t (i) VALUES (1)
    INSERT INTO #t (i) VALUES (2)
    BEGIN TRY
        INSERT INTO #t (i) VALUES (3)
        INSERT INTO #t (i) VALUES (1) -- dup key error
    END TRY
    BEGIN CATCH
        SELECT ERROR_MESSAGE()
    END CATCH  
    INSERT INTO #t (i) VALUES (4)
    /* Error the Current Transaction cannot be committed and 
    cannot support operations that write to the log file. Roll back the transaction. */

COMMIT TRAN
SELECT * FROM #t

Detectamos o erro de chave duplicada, mas, fora isso, não estamos em melhor situação. Nosso lote ainda é encerrado e nossa transação ainda é revertida. O motivo é realmente muito simples:

Os blocos TRY/CATCH não afetam as transações.

Por ter XACT_ABORT ON, no momento em que ocorre o erro de chave duplicada, a transação está condenada. Está feito. Foi fatalmente ferido. Foi um tiro no coração... e o erro é o culpado. TRY/CATCH dá ao SQL Server... um nome ruim. (desculpe, não resisti)

Em outras palavras, ele NUNCA comprometer e irá SEMPRE ser revertido. Tudo o que um bloco TRY/CATCH pode fazer é impedir a queda do cadáver. Podemos usar o XACT_STATE() função para ver se nossa transação é confirmável. Se não for, a única opção é reverter a transação.
SET XACT_ABORT ON -- Try with it OFF as well.
IF object_id('tempdb..#t') IS NOT NULL
    DROP TABLE #t
CREATE TABLE #t (i INT NOT NULL PRIMARY KEY)

BEGIN TRAN
    INSERT INTO #t (i) VALUES (1)
    INSERT INTO #t (i) VALUES (2)

    SAVE TRANSACTION Save1
    BEGIN TRY
        INSERT INTO #t (i) VALUES (3)
        INSERT INTO #t (i) VALUES (1) -- dup key error
    END TRY
    BEGIN CATCH
        SELECT ERROR_MESSAGE()
        IF XACT_STATE() = -1 -- Transaction is doomed, Rollback everything.
            ROLLBACK TRAN
        IF XACT_STATE() = 1 --Transaction is commitable, we can rollback to a save point
            ROLLBACK TRAN Save1
    END CATCH  
    INSERT INTO #t (i) VALUES (4)

IF @@TRANCOUNT > 0
    COMMIT TRAN
SELECT * FROM #t

Triggers sempre executam dentro do contexto de uma transação, então se você puder evitar o uso de TRY/CATCH dentro deles, as coisas são muito mais simples.

Para uma solução para o seu problema, um CLR Stored Proc pode se conectar novamente ao SQL Server em uma conexão separada para executar o SQL dinâmico. Você ganha a capacidade de executar o código em uma nova transação e a lógica de tratamento de erros é fácil de escrever e fácil de entender em C#.