Sqlserver
 sql >> Database >  >> RDS >> Sqlserver

Errore trigger:non è possibile eseguire il commit della transazione corrente e non è in grado di supportare operazioni che scrivono nel file di registro

Questo errore si verifica quando si utilizza un blocco try/catch all'interno di una transazione. Consideriamo un esempio banale:

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 il quarto inserimento provoca un errore, il batch viene terminato e la transazione viene ripristinata. Nessuna sorpresa finora.

Ora proviamo a gestire quell'errore con un blocco 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

Abbiamo rilevato l'errore della chiave duplicata, ma per il resto non stiamo meglio. Il nostro batch viene comunque terminato e la nostra transazione viene ancora annullata. Il motivo è in realtà molto semplice:

I blocchi TRY/CATCH non influiscono sulle transazioni.

A causa dell'attivazione di XACT_ABORT, nel momento in cui si verifica l'errore della chiave duplicata, la transazione è condannata. È fatto per. È stato ferito a morte. È stato colpito al cuore... e la colpa è dell'errore. TRY/CATCH dà a SQL Server... una cattiva reputazione. (scusate, non ho resistito)

In altre parole, MAI si impegnerà e lo farà SEMPRE essere ripristinato. Tutto ciò che un blocco TRY/CATCH può fare è interrompere la caduta del cadavere. Possiamo usare XACT_STATE() funzione per vedere se la nostra transazione è vincolabile. In caso contrario, l'unica opzione è annullare la transazione.

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

I trigger vengono sempre eseguiti nel contesto di una transazione, quindi se puoi evitare di usare TRY/CATCH al loro interno, le cose sono molto più semplici.

Per una soluzione al tuo problema, un CLR Stored Proc potrebbe riconnettersi a SQL Server in una connessione separata per eseguire l'SQL dinamico. Ottieni la possibilità di eseguire il codice in una nuova transazione e la logica di gestione degli errori è sia facile da scrivere che da capire in C#.