SQL Server - transakcje wycofują się po błędzie?

193

Mamy aplikację kliencką, która działa na SQL Server 2005, na przykład:

BEGIN TRAN;
INSERT INTO myTable (myColumns ...) VALUES (myValues ...);
INSERT INTO myTable (myColumns ...) VALUES (myValues ...);
INSERT INTO myTable (myColumns ...) VALUES (myValues ...);
COMMIT TRAN;

Jest wysyłany przez jedno długie polecenie.

Jeśli jedno z wstawień nie powiedzie się lub którakolwiek część polecenia się nie powiedzie, czy SQL Server wycofuje transakcję? Jeśli nie można go wycofać, czy muszę wysłać drugie polecenie, aby je wycofać?

Mogę podać szczegóły dotyczące interfejsu API i języka, którego używam, ale wydaje mi się, że SQL Server powinien odpowiadać tak samo dla każdego języka.

jonathanpeppers
źródło

Odpowiedzi:

204

Możesz postawić set xact_abort onprzed transakcją, aby upewnić się, że sql automatycznie cofa się w przypadku błędu.

Greg B.
źródło
1
Czy będzie działać na MS SQL 2K i nowszych? To wydaje się najprostsze rozwiązanie.
jonathanpeppers
1
Pojawia się w dokumentach z lat 2000, 2005 i 2008, więc zakładam tak. Używamy go w 2008 roku.
8
Czy muszę to wyłączyć, czy jest to sesja?
Marc
5
@Marc zakres xact_abortjest na poziomie połączenia.
Keith
2
@AlexMcMillan Instrukcja DROP PROCEDURE modyfikuje strukturę bazy danych, w przeciwieństwie do INSERT, który po prostu działa z danymi. Dlatego nie można go zawrzeć w transakcji. Upraszczam, ale w zasadzie tak to jest.
eksortso
195

Masz rację, ponieważ cała transakcja zostanie wycofana. Powinieneś wydać polecenie, aby je wycofać.

Możesz owinąć to w TRY CATCHblok w następujący sposób

BEGIN TRY
    BEGIN TRANSACTION

        INSERT INTO myTable (myColumns ...) VALUES (myValues ...);
        INSERT INTO myTable (myColumns ...) VALUES (myValues ...);
        INSERT INTO myTable (myColumns ...) VALUES (myValues ...);

    COMMIT TRAN -- Transaction Success!
END TRY
BEGIN CATCH
    IF @@TRANCOUNT > 0
        ROLLBACK TRAN --RollBack in case of Error

    -- you can Raise ERROR with RAISEERROR() Statement including the details of the exception
    RAISERROR(ERROR_MESSAGE(), ERROR_SEVERITY(), 1)
END CATCH
Raj More
źródło
2
Bardziej podoba mi się rozwiązanie DyingCactus, jego jeden wiersz kodu do zmiany. Jeśli twój, jeśli z jakiegoś powodu lepszy (lub bardziej niezawodny) daj mi znać.
jonathanpeppers
13
Try catch daje możliwość uchwycenia (i ewentualnie naprawienia) błędu i wygenerowania niestandardowego komunikatu o błędzie w razie potrzeby.
Raj Więcej
10
Myślę, że „przechwytywanie i rejestrowanie” częściej niż „przechwytywanie i naprawa”.
quillbreaker
24
Składnia RAISERROR jest nieprawidłowa przynajmniej w SQL Server 2008R2 i nowszych. Zobacz msdn.microsoft.com/en-us/library/ms178592.aspx do poprawnej składni.
Eric J.,
2
@BornToCode Aby upewnić się, że transakcja istnieje. Powiedzmy, że wycofałeś transakcję pod określonymi warunkami (w try), ale kod zawiedzie później. Nie ma już transakcji, ale nadal wchodzisz w catch.
Gabriel GM,
42

Oto kod z otrzymywaniem komunikatu o błędzie podczas pracy z MSSQL Server 2016:

BEGIN TRY
    BEGIN TRANSACTION 
        -- Do your stuff that might fail here
    COMMIT
END TRY
BEGIN CATCH
    IF @@TRANCOUNT > 0
        ROLLBACK TRAN

        DECLARE @ErrorMessage NVARCHAR(4000) = ERROR_MESSAGE()
        DECLARE @ErrorSeverity INT = ERROR_SEVERITY()
        DECLARE @ErrorState INT = ERROR_STATE()

    -- Use RAISERROR inside the CATCH block to return error  
    -- information about the original error that caused  
    -- execution to jump to the CATCH block.  
    RAISERROR (@ErrorMessage, @ErrorSeverity, @ErrorState);
END CATCH
samwise
źródło
1
Musiałem użyć DECLARE @Var TYPE; SET @Var = ERROR;do zgłaszania błędów na serwerze SQL 2005. W przeciwnym razie powyższy kod do zgłaszania błędów działa również dla starszych DB. Przyczyną problemu była próba przypisania wartości domyślnej do zmiennej lokalnej.
jtlindsey
Możesz użyć prostego RZUTU; zamiast deklaracji RAISERROR i ERROR_ *.
rodzmkii
21

Z artykułu MDSN, Kontrolowanie transakcji ( aparat bazy danych) .

Jeśli błąd zestawu w czasie wykonywania (taki jak naruszenie ograniczenia) wystąpi w partii, domyślnym zachowaniem w aparacie bazy danych jest wycofanie tylko instrukcji, która wygenerowała błąd. Możesz zmienić to zachowanie za pomocą instrukcji SET XACT_ABORT. Po wykonaniu instrukcji SET XACT_ABORT ON każdy błąd instrukcji w czasie wykonywania powoduje automatyczne wycofanie bieżącej transakcji. SET XACT_ABORT nie wpływa na błędy kompilacji, takie jak błędy składniowe. Aby uzyskać więcej informacji, zobacz ZESTAW XACT_ABORT (Transact-SQL).

W twoim przypadku spowoduje to wycofanie całej transakcji, gdy którykolwiek z wkładek zawiedzie.

Witalij
źródło
3
czego potrzebujemy do obsługi błędów składniowych? lub błędy kompilacji? jeśli
którykolwiek
Łapanie błędów kompilacji / składni jest tym, do czego służą projekty SSDT. :-)
Joe the Coder
10

Jeśli jedno z wstawień ulegnie awarii lub którakolwiek część polecenia się nie powiedzie, czy serwer SQL wycofuje transakcję?

Nie.

Jeśli nie można go wycofać, czy muszę wysłać drugie polecenie, aby je wycofać?

Jasne, powinieneś wydać ROLLBACKzamiast COMMIT.

Jeśli chcesz zdecydować, czy chcesz zatwierdzić, czy wycofać transakcję, powinieneś usunąć COMMITzdanie z wyciągu, sprawdzić wyniki wstawek, a następnie wydać albo, COMMITalbo w ROLLBACKzależności od wyników kontroli.

Quassnoi
źródło
Jeśli więc wystąpi błąd, powiedz „Konflikt klucza podstawowego”. Muszę wysłać drugie wezwanie do wycofania? To chyba ma sens. Co się stanie, jeśli wystąpi błąd związany z siecią, taki jak połączenie zostanie zerwane podczas bardzo długiej instrukcji SQL?
jonathanpeppers
2
Kiedy połączenie wygasa, bazowy protokół sieciowy (np. Named PipesLub TCP) przerywa połączenie. Gdy połączenie zostanie zerwane, SQL Serverzatrzymuje wszystkie aktualnie uruchomione polecenia i wycofuje transakcję.
Quassnoi,
1
Dzięki temu rozwiązanie DyingCactus rozwiązuje mój problem, dziękuję za pomoc.
jonathanpeppers
Jeśli musisz przerwać dowolny błąd, to tak, jest to najlepsza opcja.
Quassnoi,