Sqlserver
 sql >> Base de données >  >> RDS >> Sqlserver

Erreur de déclenchement :la transaction en cours ne peut pas être validée et ne peut pas prendre en charge les opérations qui écrivent dans le fichier journal

Cette erreur se produit lorsque vous utilisez un bloc try/catch à l'intérieur d'une transaction. Prenons un exemple 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

Lorsque la quatrième insertion provoque une erreur, le lot est terminé et la transaction est annulée. Pas de surprise jusqu'à présent.

Essayons maintenant de gérer cette erreur avec un bloc 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

Nous avons attrapé l'erreur de clé en double, mais sinon, nous ne sommes pas mieux lotis. Notre lot est toujours terminé et notre transaction est toujours annulée. La raison est en fait très simple :

Les blocs TRY/CATCH n'affectent pas les transactions.

En raison de XACT_ABORT ON, au moment où l'erreur de clé en double se produit, la transaction est vouée à l'échec. C'est fait pour. Il a été mortellement blessé. Il a été tiré dans le cœur... et l'erreur est à blâmer. TRY/CATCH donne à SQL Server... une mauvaise réputation. (désolé, je n'ai pas pu résister)

En d'autres termes, il ne sera JAMAIS s'engager et le fera TOUJOURS être annulé. Tout ce qu'un bloc TRY/CATCH peut faire, c'est interrompre la chute du cadavre. Nous pouvons utiliser le XACT_STATE() fonction pour voir si notre transaction est committable. Si ce n'est pas le cas, la seule option consiste à annuler la transaction.

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

Les déclencheurs s'exécutent toujours dans le contexte d'une transaction, donc si vous pouvez éviter d'utiliser TRY/CATCH à l'intérieur, les choses sont beaucoup plus simples.

Pour une solution à votre problème, un CLR Stored Proc peut se reconnecter à SQL Server dans une connexion distincte pour exécuter le SQL dynamique. Vous gagnez la possibilité d'exécuter le code dans une nouvelle transaction et la logique de gestion des erreurs est à la fois facile à écrire et facile à comprendre en C#.