sql >> Base de Datos >  >> RDS >> Sqlserver

Error de activación:la transacción actual no se puede confirmar y no admite operaciones que escriban en el archivo de registro

Este error ocurre cuando usa un bloque try/catch dentro de una transacción. Consideremos un ejemplo 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

Cuando la cuarta inserción provoca un error, el lote finaliza y la transacción se revierte. Sin sorpresas hasta el momento.

Ahora intentemos manejar ese error con un bloque 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 el error de clave duplicada, pero de lo contrario, no estamos mejor. Nuestro lote aún se cancela y nuestra transacción aún se revierte. La razón es realmente muy simple:

Los bloques TRY/CATCH no afectan las transacciones.

Debido a que XACT_ABORT está activado, en el momento en que ocurre el error de clave duplicada, la transacción está condenada. Está hecho para. Ha sido herido de muerte. Le han disparado en el corazón... y el error es el culpable. TRY/CATCH le da a SQL Server... un mal nombre. (lo siento, no pude resistir)

En otras palabras, NUNCA comprometerse y SIEMPRE retroceder. Todo lo que puede hacer un bloque TRY/CATCH es detener la caída del cadáver. Podemos usar el XACT_STATE() función para ver si nuestra transacción se puede comprometer. Si no es así, la única opción es revertir la transacción.

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

Los disparadores siempre se ejecutan dentro del contexto de una transacción, por lo que si puede evitar usar TRY/CATCH dentro de ellos, las cosas son mucho más simples.

Para una solución a su problema, un CLR Stored Proc podría volver a conectarse a SQL Server en una conexión separada para ejecutar el SQL dinámico. Obtiene la capacidad de ejecutar el código en una nueva transacción y la lógica de manejo de errores es fácil de escribir y fácil de entender en C#.