2013-04-12 5 views
8

In questo modo viene visualizzato il seguente messaggio di errore da SQL Server quando sp_SomeProc tenta di eseguire un'istruzione SQL valida. Ottengo l'errore:Errore di trigger: la transazione corrente non può essere confermata e non supporta le operazioni che scrivono nel file di registro

The current transaction cannot be committed and cannot support operations that write to the log file. 

Tutte le idee su quello che sto facendo male? ("Perché stai facendo questo" questo è solo un esempio che ho creato per imitare il problema quindi per favore no "questo ha implicazioni per la sicurezza", ecc ..)


Quindi la mia tabella assomiglia a:

CREATE TABLE tSOMETABLE 
( 
    RecID INT NOT NULL IDENTITY(1,1) 
    Val VARCHAR(20), 
CONSTRAINT [PK_tSOMETABLE] PRIMARY KEY CLUSTERED 
(
    RecID ASC 
) 
) 

Così nel mio grilletto ho:

CREATE TRIGGER [dbo].[TR_tSOMETABLE_INSERT]  
    ON [dbo].[tSOMETABLE] 
    FOR INSERT 
AS  
SET NOCOUNT ON 
BEGIN 
     BEGIN 
      SELECT * INTO #temp FROM INSERTED 

      WHILE EXISTS (SELECT 1 FROM #temp) 
      BEGIN 
       DECLARE @RecID INT  
       SELECT @RecID = RecID 
       FROM #temp t 
       EXEC dbo.sp_SomeProc @EventType = 'ON INSERT', @RecID = @RecID 
       DELETE #temp WHERE @RecID = RecID 
      END   
     END 
END 

Ora il codice di sp_SomeProc assomiglia:

CREATE PROC sp_SomeProc 
(
    @EventType VARCHAR(50), 
    @RecID INT, 
    @Debug BIT = 0 
) 
AS 
BEGIN 
    SET NOCOUNT ON 

    DECLARE @ProcTable TABLE 
    (
     RecID INT NOT NULL IDENTITY(1,1), 
     Cmd VARCHAR(MAX) 
    ) 

    INSERT INTO @ProcTable(Cmd) 
     SELECT 'EXEC sp_who' 
     UNION 
     SELECT 'EXEC sp_SomeStoredProcThatDoesntExist' 


    DECLARE @RecID INT 
    SELECT @RecID = MIN(RecID) FROM @ProcTable 
    WHILE @RecID IS NOT NULL 
    BEGIN 
     DECLARE @sql VARCHAR(MAX) 
     SELECT @sql = cmd FROM @ProcTable WHERE RecID = @RecID 
     IF @Debug = 1 
      PRINT @sql 
     ELSE 
      BEGIN 
       BEGIN TRY  
        EXEC(@sql) 
       END TRY 
       BEGIN CATCH 
        DECLARE @Msg VARCHAR(MAX), @ErrorNumber INT, @ErrorSeverity INT, @ErrorState int, @ErrorProcedure nvarchar(256), @ErrorLine int, @ErrorMessage nvarchar(MAX) 
        SELECT @Msg = 'Failed While Executing: ' + @sql 
        SELECT @ErrorNumber = ERROR_NUMBER(), @ErrorSeverity = ERROR_SEVERITY(), @ErrorState = ERROR_STATE(), @ErrorProcedure = ERROR_PROCEDURE(), @ErrorLine = ERROR_LINE(), @ErrorMessage = ERROR_MESSAGE() 
        -- DO SOME MORE STUFF HERE AND THEN ... 
        RAISERROR(@ErrorMessage, @ErrorSeverity, @ErrorState) 
       END CATCH 
      END 
     SELECT @RecID = MIN(RecID) FROM @ProcTable WHERE RecID > @RecID 
    END 
END 

Quindi, per testare provo:

INSERT INTO tSOMETABLE(Val) 
SELECT 'Hello' 
+0

Torna alle basi. Qual è in realtà il trigger che sta cercando di fare? Perché non puoi farlo come un'operazione basata su set all'interno del trigger usando 'inserted' invece di eseguire il ciclo di riga per riga dolorosa ed eseguire una stored procedure separata e complicata per ogni riga? –

+0

Impossibile, il trigger deve eseguire alcuni processi memorizzati per ogni riga che viene inserita in base ai dati che stanno entrando nella riga. È necessario eseguire lo sql in modo dinamico e non può essere eseguito come un'operazione predefinita. Non ho alcun controllo sui processi memorizzati che esegue per ogni riga. Nell'implementazione completa c'è una tabella che decide quale SQL eseguire per ogni riga di INSERTED (ma che è irrilevante per questa domanda) – Denis

risposta

38

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 inserto causa un errore, il lotto è terminato e la transazione rollback. Nessuna sorpresa finora.

Ora cerchiamo di tentare di gestire tale 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 preso l'errore di chiave duplicata, ma per il resto, non siamo meglio. Il nostro batch viene ancora terminato e la nostra transazione viene comunque ripristinata. 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 mortalmente. È stato sparato attraverso il cuore ... e l'errore è la colpa. TRY/CATCH fornisce a SQL Server ... un brutto nome. (Spiacente, non poteva resistere)

In altre parole, esso MAI impegnarsi e verrà SEMPRE essere abortita. Tutto ciò che un blocco TRY/CATCH può fare è interrompere la caduta del cadavere. Possiamo utilizzare la funzione XACT_STATE() per verificare se la nostra transazione è commitabile. In caso contrario, l'unica opzione è il rollback della 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 

Trigger eseguire sempre nel contesto di una transazione, quindi se si può evitare di usare try/catch al loro interno, le cose sono molto più semplici.

Per una soluzione al vostro problema, una stored procedure CLR potrebbe connettersi 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 è facile da scrivere e facile da comprendere in C#.

+6

+1 per il riferimento a Bon Jovi;) Risposta utile anche. Grazie! – t0mm0d

+1

Cosa succede se ricevo lo stesso errore e non sono in una transazione? Sto usando un try/catch per provare e analizzare date diverse. Excel usa un "numero di serie", ma altri usano una data normale. Inoltre, se è SQL dinamico e una transazione, l'errore, se catturato, si arresta alla transazione? –

+1

Ricevo questo errore anche se non sto utilizzando TRANSACTIONS, mi sto collegando a diverse istanze di database tramite server collegato. – Muflix

Problemi correlati