Sqlserver
 sql >> Datenbank >  >> RDS >> Sqlserver

Auslösefehler:Die aktuelle Transaktion kann nicht festgeschrieben werden und kann keine Vorgänge unterstützen, die in die Protokolldatei schreiben

Dieser Fehler tritt auf, wenn Sie einen try/catch-Block innerhalb einer Transaktion verwenden. Betrachten wir ein triviales Beispiel:

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

Wenn die vierte Einfügung einen Fehler verursacht, wird der Stapel beendet und die Transaktion zurückgesetzt. Bisher keine Überraschungen.

Lassen Sie uns nun versuchen, diesen Fehler mit einem TRY/CATCH-Block zu behandeln:

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

Wir haben den doppelten Schlüsselfehler entdeckt, aber ansonsten sind wir nicht besser dran. Unser Batch wird immer noch beendet und unsere Transaktion wird immer noch zurückgesetzt. Der Grund ist eigentlich ganz einfach:

TRY/CATCH-Blöcke wirken sich nicht auf Transaktionen aus.

Da XACT_ABORT ON ist, ist die Transaktion in dem Moment, in dem der Fehler mit doppeltem Schlüssel auftritt, zum Scheitern verurteilt. Es ist erledigt. Es wurde tödlich verwundet. Es wurde ins Herz geschossen ... und der Fehler ist schuld. TRY/CATCH gibt SQL Server ... einen schlechten Namen. (sorry, konnte nicht widerstehen)

Mit anderen Worten, es wird NIE verpflichten und werden IMMER zurückgerollt werden. Alles, was ein TRY/CATCH-Block tun kann, ist, den Fall der Leiche zu stoppen. Wir können den XACT_STATE() verwenden Funktion, um zu sehen, ob unsere Transaktion festgeschrieben werden kann. Ist dies nicht der Fall, besteht die einzige Möglichkeit darin, die Transaktion rückgängig zu machen.

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

Trigger werden immer im Kontext einer Transaktion ausgeführt. Wenn Sie also die Verwendung von TRY/CATCH in ihnen vermeiden können, sind die Dinge viel einfacher.

Als Lösung für Ihr Problem könnte ein CLR Stored Proc in einer separaten Verbindung eine Verbindung mit SQL Server herstellen, um das dynamische SQL auszuführen. Sie erhalten die Möglichkeit, den Code in einer neuen Transaktion auszuführen, und die Fehlerbehandlungslogik ist sowohl einfach zu schreiben als auch in C# leicht verständlich.