2013-04-12 12 views
8

Dostaję następujący komunikat o błędzie z SQL Server, gdy sp_SomeProc spróbuje wykonać niepoprawną instrukcję sql. I pojawia się błąd:Błąd wyzwalacza: Bieżąca transakcja nie może zostać zatwierdzona i nie obsługuje operacji zapisujących do pliku dziennika.

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

jakieś pomysły na to, co robię źle? („Dlaczego to robisz” to tylko próbka że stworzony, aby naśladować problem, więc proszę nie „to ma wpływ na bezpieczeństwo”, itd ..)


Więc moja tabela wygląda następująco:

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

Więc w moim spuście mam:

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 

teraz kod sp_SomeProc wygląda następująco:

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 

Więc przetestować Staram:

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

Wracaj do podstaw. Jaki wyzwalacz faktycznie próbuje wykonać? Dlaczego nie możesz tego zrobić jako operacji opartej na zestawie wewnątrz wyzwalacza za pomocą 'wstawionego' zamiast pętli wiersza przez bolesny wiersz i wykonując oddzielną, skomplikowaną procedurę przechowywaną dla każdego wiersza? –

+0

Nie można, wyzwalacz musi wykonać niektóre przechowywane procs dla każdego wstawianego wiersza na podstawie danych, które trafiają do wiersza. Musi uruchamiać sql dynamicznie i nie można tego zrobić jako operacji set. Nie mam kontroli nad przechowywanymi procami, które wykonuje dla każdego wiersza. W pełnej implementacji znajduje się tabela, która decyduje, który sql uruchomić dla każdego wiersza INSERTED (ale to nie ma znaczenia dla tego pytania) – Denis

Odpowiedz

38

Ten błąd występuje podczas korzystania z bloku try/catch wewnątrz transakcji. Rozważmy prosty przykład:

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 

Gdy czwarta wkładka powoduje błąd, partia zostaje zakończona, a transakcja wycofana. Żadnych niespodzianek do tej pory.

Teraz próbują obsłużyć ten błąd z bloku 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 

Złapaliśmy duplikat klucza błąd, ale poza tym, nie jesteśmy lepiej. Nasza partia nadal jest usuwana, a nasza transakcja nadal jest wycofywana. Powód jest bardzo prosty:

Bloki TRY/CATCH nie mają wpływu na transakcje.

Z powodu włączenia XACT_ABORT, w momencie wystąpienia błędu duplikatu klucza, transakcja jest skazana na zaginięcie. Jest to zrobione. Został śmiertelnie ranny. Został zastrzelony przez serce ... i błąd jest winien. TRY/CATCH daje SQL Server ... złą nazwę. (Przepraszam, nie mogłem się oprzeć)

Innymi słowy, będzie NIGDY popełnić i ZAWSZE zostać wycofana. Cała blok TRY/CATCH może zepsuć upadek zwłok. Możemy użyć funkcji XACT_STATE(), aby sprawdzić, czy nasza transakcja jest możliwa do wywołania. Jeśli nie jest, jedyną opcją jest wycofanie transakcji.

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 

wyzwalacze zawsze wykonać w ramach transakcji, więc jeśli można uniknąć stosując try/catch wewnątrz nich rzeczy są znacznie prostsze.

Aby rozwiązać problem, zapisany proces CLR mógł połączyć się z serwerem SQL w osobnym połączeniu w celu wykonania dynamicznego SQL. Zyskujesz możliwość wykonywania kodu w nowej transakcji, a logika obsługi błędów jest zarówno łatwa do napisania, jak i łatwa do zrozumienia w języku C#.

+6

+1 dla odniesienia Bon Jovi;) Pomocna odpowiedź też. Dzięki! – t0mm0d

+1

Co się stanie, jeśli otrzymam ten sam błąd i nie biorę udziału w transakcji? Używam try/catch, aby spróbować przeanalizować różne daty. Program Excel używa "numeru seryjnego", ale inni używają zwykłej daty. Ponadto, jeśli jest to dynamiczny sql i transakcja, czy błąd zostanie przechwycony do transakcji? –

+1

Otrzymuję ten błąd, nawet jeśli nie używam TRANSAKCJI, łączę się z inną instancją bazy danych za pośrednictwem połączonego serwera. – Muflix