SQL Server w logistyce

Zmienna tabelowa jako bufor błędów przeżywa ROLLBACK, a trzy błędy w CATCH maskują oryginał

Wiersze zmiennej tabelowej zostają po ROLLBACK, więc opis przebiegu transakcji trafi do dziennika po wycofaniu. Trzy błędy w samym bloku CATCH przesłaniają oryginał, a wołający widzi tylko ich numer.

sql.server.net.pl/sql-server/
Programistka przy dwóch monitorach z kodem i laptopie
Programistka przy dwóch monitorach z kodem i laptopie
W skrócie
  • Problem: zapis do tabeli dziennika przed ROLLBACK jest wycofywany razem z transakcją, a po wycofaniu zostaje sam numer błędu, bez opisu przebiegu.
  • Wzorzec: zmienna tabelowa zbiera opis kroków w transakcji, a blok CATCH przepisuje go do dziennika po ROLLBACK; tabela tymczasowa tego nie zrobi.
  • Pułapka: trzy błędy w samym bloku CATCH (3930, 3903 i 6401) przesłaniają oryginalny błąd 8134 i to ich numer widzi wołający.
  • Kontrola: zapytanie liczy w dzienniku wpisy każdej z siedmiu procedur; trzy z nich zostawiają zero wpisów.

Zmienna tabelowa przeżywa ROLLBACK, więc procedura zapisu może zbierać w niej opis przebiegu transakcji i dopisać go do dziennika dopiero po wycofaniu. Wiersze tabeli tymczasowej i tabeli trwałej, wstawione w tej samej transakcji, takiego wycofania nie przetrwają.

W teście zmienna tabelowa miała po ROLLBACK nadal jeden wiersz, a tabela tymczasowa zero. Bufor działa także przy SET XACT_ABORT ON, gdy zapis do dziennika przed ROLLBACK kończy się błędem 3930. Trzy błędy w samym bloku CATCH, czyli 3930, 3903 i 6401, przesłaniają oryginalny błąd 8134, a wołający widzi numer błędu z obsługi.

To wzorzec z nazwami przykładowymi. Przykład uruchomiliśmy na SQL Server 2022 Express w jednorazowej bazie przy poziomach zgodności 130 i 160 i dał identyczne wyniki. Instrukcja THROW jest dostępna od SQL Server 2012 (według dokumentacji). Sprawdziliśmy wyłącznie wywołanie procedury z własnym blokiem TRY...CATCH po stronie wołającego.

Zapis do dziennika po ROLLBACK jako punkt wyjścia

Przykład ma dwie tabele: Dane z jedną kolumną wartości oraz Dziennik z trzema kolumnami opisu: procedura, numer błędu i komunikat. Procedury wstawiają w transakcji wiersz do Dane, a potem dzielą przez zero przez zmienną, co daje błąd 8134. W dwóch procedurach z dalszych sekcji dzielenie stoi przed BEGIN TRANSACTION.

CREATE TABLE dbo.Dane (Id int IDENTITY(1,1) PRIMARY KEY, Wartosc int NOT NULL);
CREATE TABLE dbo.Dziennik (
    Id        int IDENTITY(1,1) PRIMARY KEY,
    Czas      datetime2(0)   NOT NULL DEFAULT SYSDATETIME(),
    Procedura sysname        NOT NULL,
    Numer     int            NULL,
    Komunikat nvarchar(2048) NULL
);

Podstawy opisuje artykuł o TRY CATCH i THROW po ROLLBACK: blok CATCH, który zawiera tylko ROLLBACK i nie ma THROW, nie zgłasza błędu. W teście (ZapisSamRollback) wołający nie dostał błędu, w Dane było 0 wierszy, a @@TRANCOUNT wynosił 0, więc dla aplikacji awaria wyglądała jak sukces.

Punktem odniesienia jest zapis do dziennika po wycofaniu transakcji. Procedura ZapisProsty robi najpierw IF XACT_STATE() <> 0 ROLLBACK TRANSACTION, potem INSERT do Dziennik, a na końcu THROW;. Wołający dostał błąd 8134, w Dziennik jest jeden wpis z numerem 8134 i komunikatem „Divide by zero error encountered.”, a w Dane zero wierszy.

CREATE PROCEDURE dbo.ZapisProsty
AS
BEGIN
    SET NOCOUNT ON;
    DECLARE @zero int = 0;
    BEGIN TRY
        BEGIN TRANSACTION;
        INSERT dbo.Dane (Wartosc) VALUES (1);
        SELECT 1 / @zero AS Wynik;
        COMMIT TRANSACTION;
    END TRY
    BEGIN CATCH
        IF XACT_STATE() <> 0 ROLLBACK TRANSACTION;
        INSERT dbo.Dziennik (Procedura, Numer, Komunikat)
        VALUES (OBJECT_NAME(@@PROCID), ERROR_NUMBER(), ERROR_MESSAGE());
        THROW;
    END CATCH;
END;

Funkcje ERROR_NUMBER() i ERROR_MESSAGE() zwracają po ROLLBACK te same wartości co przed nim, więc ZapisProsty mogła użyć ich wprost w INSERT po wycofaniu. THROW; bez parametrów rzuca dalej oryginalny błąd 8134. Wersja z INSERT przed ROLLBACK, czyli ZapisZly, dała u wołającego ten sam błąd, ale w dzienniku zero wpisów.

CREATE PROCEDURE dbo.ZapisZly
AS
BEGIN
    SET NOCOUNT ON;
    DECLARE @zero int = 0;
    BEGIN TRY
        BEGIN TRANSACTION;
        INSERT dbo.Dane (Wartosc) VALUES (1);
        SELECT 1 / @zero AS Wynik;
        COMMIT TRANSACTION;
    END TRY
    BEGIN CATCH
        INSERT dbo.Dziennik (Procedura, Numer, Komunikat)
        VALUES (OBJECT_NAME(@@PROCID), ERROR_NUMBER(), ERROR_MESSAGE());
        ROLLBACK TRANSACTION;
        THROW;
    END CATCH;
END;
BEGIN TRY EXEC dbo.ZapisZly; END TRY
BEGIN CATCH SELECT ERROR_NUMBER() AS BladOdWolajacego, ERROR_MESSAGE() AS Komunikat; END CATCH;
SELECT (SELECT COUNT(*) FROM dbo.Dziennik WHERE Procedura = N'ZapisZly') AS WpisyDziennika,
       (SELECT COUNT(*) FROM dbo.Dane) AS WierszeDane, @@TRANCOUNT AS Trancount;
Wynik wywołania ZapisZly
BladOdWolajacegoKomunikatWpisyDziennikaWierszeDaneTrancount
8134Divide by zero error encountered.000

Oczekiwany wynik wywołania: błąd dociera do wołającego, ale dziennik jest pusty.

Zmienna tabelowa zachowuje wpisy po ROLLBACK, a tabela tymczasowa je traci

Procedura ZapisZBuforem wpisuje w transakcji po jednym wierszu do zmiennej tabelowej @kroki i do tabeli tymczasowej #kroki. Blok CATCH liczy wiersze obu przed ROLLBACK i po nim, a potem zapisuje do Dziennik wiersze z bufora i błąd 8134 z komunikatem, a na końcu wiersz podsumowania z licznikami.

CREATE PROCEDURE dbo.ZapisZBuforem
AS
BEGIN
    SET NOCOUNT ON;
    DECLARE @zero int = 0;
    DECLARE @kroki TABLE (Nr int IDENTITY(1,1), Opis nvarchar(200));
    CREATE TABLE #kroki (Nr int IDENTITY(1,1), Opis nvarchar(200));
    BEGIN TRY
        BEGIN TRANSACTION;
        INSERT dbo.Dane (Wartosc) VALUES (1);
        INSERT @kroki (Opis) VALUES (N'krok 1: wstawiono wartosc');
        INSERT #kroki (Opis) VALUES (N'krok 1: wstawiono wartosc');
        SELECT 1 / @zero AS Wynik;
        COMMIT TRANSACTION;
    END TRY
    BEGIN CATCH
        DECLARE @numer int = ERROR_NUMBER(), @komunikat nvarchar(2048) = ERROR_MESSAGE(),
                @wZmiennej int = (SELECT COUNT(*) FROM @kroki), @wTymczasowej int = (SELECT COUNT(*) FROM #kroki);
        IF XACT_STATE() <> 0 ROLLBACK TRANSACTION;
        INSERT dbo.Dziennik (Procedura, Numer, Komunikat)
        SELECT OBJECT_NAME(@@PROCID), NULL, Opis FROM @kroki;
        INSERT dbo.Dziennik (Procedura, Numer, Komunikat)
        VALUES (OBJECT_NAME(@@PROCID), @numer, @komunikat);
        INSERT dbo.Dziennik (Procedura, Numer, Komunikat)
        VALUES (OBJECT_NAME(@@PROCID), NULL,
                CONCAT(N'przed ROLLBACK zmienna=', @wZmiennej, N' tymczasowa=', @wTymczasowej,
                       N'; po ROLLBACK zmienna=', (SELECT COUNT(*) FROM @kroki), N' tymczasowa=', (SELECT COUNT(*) FROM #kroki)));
        THROW;
    END CATCH;
END;

Przed ROLLBACK w obu było po jednym wierszu. Po ROLLBACK zmienna miała nadal 1 wiersz, a tabela tymczasowa 0. Do Dziennik trafiły razem 3 wpisy, z czego 1 z numerem błędu, a wiersz w Dane został cofnięty (0 wierszy, @@TRANCOUNT równe 0). Bufor chroni więc wyłącznie własną zawartość.

Dziennik po wywołaniu ZapisZBuforem
ProceduraNumerKomunikat
ZapisZBuforemNULLkrok 1: wstawiono wartosc
ZapisZBuforem8134Divide by zero error encountered.
ZapisZBuforemNULLprzed ROLLBACK zmienna=1 tymczasowa=1; po ROLLBACK zmienna=1 tymczasowa=0

Wynik zapytania do dziennika po jednym wywołaniu procedury.

Zmienna tabelowa zachowuje zawartość po ROLLBACK, więc opis przebiegu transakcji zbieraj w niej i zapisuj do dziennika dopiero po wycofaniu.

Oś czasu transakcji: po ROLLBACK zmienna tabelowa zachowuje wpis, a tabela tymczasowa i tabela Dane go tracą Schemat pokazuje oś czasu: po ROLLBACK tabela Dane i tabela tymczasowa mają 0 wierszy, a zmienna tabelowa 1 wiersz, który trafia do dziennika. Po ROLLBACK zostaje wpis w zmiennej tabelowej, a tabela tymczasowa go traci Oś czasu transakcji z błędem 8134: bufor w zmiennej tabelowej trafia do dziennika dopiero po wycofaniu. ROLLBACK BEGIN TRANSACTION INSERT Dane, wpis do @kroki i do #kroki błąd 8134 CATCH: ROLLBACK INSERT z bufora do Dziennik THROW Dane (tabela trwała) 1 wiersz po ROLLBACK 0 #kroki (tabela tymczasowa) 1 wiersz po ROLLBACK 0 @kroki (zmienna tabelowa) 1 wiersz po ROLLBACK 1 Dziennik: 3 wpisy SET XACT_ABORT ON, stan -1 INSERT do Dziennik przed ROLLBACK: błąd 3930 INSERT do @kroki przed ROLLBACK: działa
Schemat pokazuje oś czasu: po ROLLBACK tabela Dane i tabela tymczasowa mają 0 wierszy, a zmienna tabelowa 1 wiersz, który trafia do dziennika.

Ta różnica dotyczy wierszy wstawionych w transakcji. Tabela tymczasowa wypełniona przed jej rozpoczęciem to inna sytuacja, której używa na przykład artykuł o zmianie zapytań w trybie próbnym z ROLLBACK. Samą zmienną opisuje dokumentacja typu table, a zmienną tabelową w klauzuli OUTPUT INTO pokazuje artykuł o znaczniku wysłano w jednym UPDATE.

Bufor działa także przy XACT_ABORT ON, gdy zapis do dziennika kończy się błędem 3930

Przy SET XACT_ABORT ON błąd w bloku TRY ustawia XACT_STATE() na -1. Zły wzorzec z INSERT przed ROLLBACK (procedura ZapisZlyXact to ZapisZly z dopisanym SET XACT_ABORT ON;) kończy się wtedy błędem 3930, który przesłania błąd 8134. Wpisu nie ma, a transakcja zostaje otwarta: po powrocie @@TRANCOUNT wynosił 1, XACT_STATE() wołającego -1, a w Dane widać wiersz do czasu wycofania.

W procedurze ZapisBuforXact ten sam moment wygląda inaczej. INSERT do zmiennej tabelowej w bloku CATCH przed ROLLBACK kończy się powodzeniem, choć zapis do Dziennik w tym samym miejscu daje błąd 3930. Po ROLLBACK procedura przepisuje bufor do dziennika, razem trzy wpisy: wiersz kroku, wiersz z wartością stanu transakcji i błąd 8134.

CREATE PROCEDURE dbo.ZapisBuforXact
AS
BEGIN
    SET NOCOUNT ON;
    SET XACT_ABORT ON;
    DECLARE @zero int = 0;
    DECLARE @kroki TABLE (Nr int IDENTITY(1,1), Opis nvarchar(200));
    BEGIN TRY
        BEGIN TRANSACTION;
        INSERT dbo.Dane (Wartosc) VALUES (1);
        INSERT @kroki (Opis) VALUES (N'krok 1: wstawiono wartosc');
        SELECT 1 / @zero AS Wynik;
        COMMIT TRANSACTION;
    END TRY
    BEGIN CATCH
        DECLARE @numer int = ERROR_NUMBER(), @komunikat nvarchar(2048) = ERROR_MESSAGE(), @stan int = XACT_STATE();
        INSERT @kroki (Opis) VALUES (CONCAT(N'CATCH: XACT_STATE = ', @stan));
        IF @stan <> 0 ROLLBACK TRANSACTION;
        INSERT dbo.Dziennik (Procedura, Numer, Komunikat)
        SELECT OBJECT_NAME(@@PROCID), NULL, Opis FROM @kroki;
        INSERT dbo.Dziennik (Procedura, Numer, Komunikat)
        VALUES (OBJECT_NAME(@@PROCID), @numer, @komunikat);
        THROW;
    END CATCH;
END;
Dziennik po wywołaniu ZapisBuforXact
ProceduraNumerKomunikat
ZapisBuforXactNULLkrok 1: wstawiono wartosc
ZapisBuforXactNULLCATCH: XACT_STATE = -1
ZapisBuforXact8134Divide by zero error encountered.

Wynik jest taki sam na poziomach zgodności 130 i 160. Wołający dostał błąd 8134, w Dane było 0 wierszy, a @@TRANCOUNT wynosił 0.

Szczegóły błędu 3930, czyli zapisu do dziennika w stanie -1, omawia artykuł o procedurze zapisu z THROW po ROLLBACK, razem z wywołaniem z cudzej transakcji. Tutaj nie sprawdzaliśmy zachowania bufora w cudzej transakcji ani w innej sesji.

Błąd 3903 przesłania oryginał, gdy ROLLBACK stoi przed BEGIN TRANSACTION

W procedurze ZapisPrzedTransakcjaBezSprawdzenia dzielenie przez zero stoi przed BEGIN TRANSACTION, a blok CATCH zaczyna się od bezwarunkowego ROLLBACK. Transakcji jeszcze nie ma, więc ROLLBACK kończy się błędem 3903 (brak odpowiadającego BEGIN TRANSACTION), który przesłania błąd 8134. Wołający dostał 3903, a wpisu w dzienniku nie ma, bo INSERT stoi za ROLLBACK.

CREATE PROCEDURE dbo.ZapisPrzedTransakcjaBezSprawdzenia
AS
BEGIN
    SET NOCOUNT ON;
    DECLARE @zero int = 0;
    BEGIN TRY
        SELECT 1 / @zero AS Wynik;
        BEGIN TRANSACTION;
        INSERT dbo.Dane (Wartosc) VALUES (1);
        COMMIT TRANSACTION;
    END TRY
    BEGIN CATCH
        ROLLBACK TRANSACTION;
        INSERT dbo.Dziennik (Procedura, Numer, Komunikat)
        VALUES (OBJECT_NAME(@@PROCID), ERROR_NUMBER(), ERROR_MESSAGE());
        THROW;
    END CATCH;
END;

Ta sama procedura z warunkiem IF XACT_STATE() <> 0 ROLLBACK TRANSACTION; zamiast samego ROLLBACK (ZapisPrzedTransakcjaZeSprawdzeniem) zachowuje się poprawnie. Wołający dostał oryginalny błąd 8134, a w Dziennik powstał 1 wpis z numerem 8134. Funkcję opisuje dokumentacja XACT_STATE.

Błąd 6401 pojawia się, gdy przed THROW brakuje średnika

Według dokumentacji instrukcja THROW wymaga średnika w poprzedniej instrukcji. W procedurze ZapisBezSrednika polecenie ROLLBACK TRANSACTION bez średnika przed THROW; zostało odczytane jako wycofanie transakcji o nazwie THROW. Wynik to błąd 6401 (Cannot roll back THROW. No transaction or savepoint of that name was found.), a transakcja pozostała otwarta: @@TRANCOUNT wynosił 1, a w Dane widoczny był wiersz.

CREATE PROCEDURE dbo.ZapisBezSrednika
AS
BEGIN
    SET NOCOUNT ON;
    DECLARE @zero int = 0;
    BEGIN TRY
        BEGIN TRANSACTION;
        INSERT dbo.Dane (Wartosc) VALUES (1);
        SELECT 1 / @zero AS Wynik;
        COMMIT TRANSACTION;
    END TRY
    BEGIN CATCH
        ROLLBACK TRANSACTION
        THROW;
    END CATCH;
END;

W trzech opisanych przypadkach wołający dostał numer błędu z samej obsługi, a nie oryginalny 8134: 3930 w jednym teście, 3903 w drugim i 6401 w trzecim. Przyczyna, czyli dzielenie przez zero, nie dociera do wołającego w żadnym z nich. Numery i komunikaty sprawdziliśmy na SQL Server 2022 przy poziomach zgodności 130 i 160 i nie twierdzimy, że są takie same w każdej wersji.

Zapytanie kontrolne pokazuje, która procedura zostawia wpis

Tabela zbiera wszystkie warianty. Kolumna z błędem u wołającego to numer, który dostała aplikacja, a ostatnia kolumna to wpisy w dzienniku po jednym wywołaniu procedury.

Osiem wariantów procedury zapisu
ProceduraXACT_ABORTKolejność w CATCHBłąd u wołającegoWpisy w dzienniku
ZapisZlyOFFINSERT, ROLLBACK, THROW81340
ZapisProstyOFFROLLBACK warunkowy, INSERT, THROW81341
ZapisZBuforemOFFROLLBACK, INSERT z bufora i błędu, THROW81343
ZapisZlyXactONINSERT (przerwany), ROLLBACK, THROW39300
ZapisBuforXactONzapis do bufora, ROLLBACK, INSERT z bufora i błędu, THROW81343
ZapisPrzedTransakcjaBezSprawdzeniaOFFROLLBACK bez warunku (przerwany), INSERT, THROW39030
ZapisPrzedTransakcjaZeSprawdzeniemOFFROLLBACK warunkowy, INSERT, THROW81341
ZapisBezSrednikaOFFROLLBACK bez średnika, THROW6401brak instrukcji INSERT, transakcja zostaje otwarta

Przykład ilustracyjny. Wyniki z SQL Server 2022 przy poziomach zgodności 130 i 160.

Zapytanie kontrolne obejmuje siedem procedur (bez ZapisBezSrednika, która nie zapisuje do dziennika) i liczy w Dziennik wszystkie wpisy oraz wpisy z numerem błędu. Trzy procedury zostawiają zero wpisów: ZapisPrzedTransakcjaBezSprawdzenia, ZapisZly i ZapisZlyXact.

SELECT p.Nazwa AS Procedura, COUNT(d.Id) AS WpisyDziennika, COUNT(d.Numer) AS WpisyZNumeremBledu
FROM (VALUES (N'ZapisZly'), (N'ZapisProsty'), (N'ZapisZBuforem'), (N'ZapisZlyXact'), (N'ZapisBuforXact'),
             (N'ZapisPrzedTransakcjaBezSprawdzenia'), (N'ZapisPrzedTransakcjaZeSprawdzeniem')) AS p(Nazwa)
LEFT JOIN dbo.Dziennik d ON d.Procedura = p.Nazwa
GROUP BY p.Nazwa ORDER BY p.Nazwa;
Wynik zapytania kontrolnego
ProceduraWpisyDziennikaWpisyZNumeremBledu
ZapisBuforXact31
ZapisProsty11
ZapisPrzedTransakcjaBezSprawdzenia00
ZapisPrzedTransakcjaZeSprawdzeniem11
ZapisZBuforem31
ZapisZly00
ZapisZlyXact00

Wiersze z zerem wpisów to warianty, w których błąd obsługi przesłonił oryginał albo zapis stał przed ROLLBACK.

Siatka kontrolna: siedem procedur z liczbą wpisów w dzienniku, trzy z zerem na czerwonym tle Makieta poglądowa, dane przykładowe. Siatka kontrolna z siedmioma procedurami, w której trzy wiersze z zerem wpisów mają czerwone tło. Wyniki Procedura WpisyDziennika WpisyZNumeremBledu ZapisBuforXact 3 1 ZapisProsty 1 1 ZapisPrzedTransakcjaBezSprawdzenia 0 0 ZapisPrzedTransakcjaZeSprawdzeniem 1 1 ZapisZBuforem 3 1 ZapisZly 0 0 ZapisZlyXact 0 0 7 rows Makieta poglądowa, dane przykładowe
Makieta poglądowa, dane przykładowe. Siatka kontrolna z siedmioma procedurami, w której trzy wiersze z zerem wpisów mają czerwone tło.

Przykład: procedura zapisuje dokument 26-3001 w transakcji i po każdym kroku dopisuje wiersz do zmiennej tabelowej. Przy trzeciej pozycji pojawia się błąd. Blok CATCH sprawdza stan transakcji, wycofuje ją i dopiero wtedy przepisuje bufor do dziennika, więc administrator widzi, które kroki zdążyły się wykonać przed błędem. W innej procedurze blok CATCH ma bezwarunkowy ROLLBACK, a błąd wystąpił przed otwarciem transakcji, więc wołający dostaje błąd o braku BEGIN TRANSACTION zamiast oryginalnego.

Procedurę z blokadą i blokiem CATCH, który zwalnia zasób, pokazuje artykuł o idempotencji wysyłki w T-SQL, a kolejny szablon przykładu z danymi próbnymi artykuł o bilansie pozycji zamówienia. Błędy 2601 i 2627 rozpoznawane w CATCH omawia artykuł o indeksie filtrowanym przy miękkim usuwaniu, a wszystkie artykuły sekcji zebraliśmy w spisie artykułów o SQL Server.

Cztery pytania przed budową własnej obsługi błędów

Zanim przeniesiesz wzorzec do własnej bazy, odpowiedz sobie na cztery pytania.

  • Dziennik: czy dziennik błędów zawiera tylko numer i komunikat, czy także opis tego, co transakcja zdążyła zrobić przed błędem.
  • XACT_STATE: czy ROLLBACK w bloku CATCH jest poprzedzony sprawdzeniem XACT_STATE() także w procedurach, w których błąd może wystąpić przed BEGIN TRANSACTION.
  • Średnik: czy instrukcja przed każdym THROW kończy się średnikiem.
  • Numer błędu: jaki numer widzi aplikacja po awarii, oryginalny czy numer błędu z samego bloku CATCH.
FAQ

Najczęstsze pytania o zmienną tabelową i ROLLBACK

01

Czy zmienna tabelowa przeżywa ROLLBACK, a tabela tymczasowa nie?

W teście procedura wpisała w transakcji po jednym wierszu do zmiennej tabelowej i do tabeli tymczasowej. Po ROLLBACK zmienna miała nadal 1 wiersz, a tabela tymczasowa 0. Wiersz w tabeli Dane został cofnięty, bo bufor chroni wyłącznie własną zawartość.

02

Po co bufor, skoro wystarczy zapis do dziennika po ROLLBACK?

Zapis po ROLLBACK zostawia numer błędu i komunikat, ale nie opis tego, co transakcja zdążyła zrobić. Bufor zbiera wpisy w trakcie transakcji, a po wycofaniu trafiają one do dziennika razem z błędem, w teście 3 wpisy. Bufor działa też w stanie -1, w którym zapis do dziennika przed ROLLBACK daje błąd 3930.

03

Co się dzieje przy XACT_ABORT ON z zapisem do dziennika, a co z zapisem do zmiennej tabelowej?

Przy SET XACT_ABORT ON błąd w TRY ustawia XACT_STATE() na -1. Zapis do dziennika przed ROLLBACK kończy się wtedy błędem 3930, który przesłania błąd 8134, a transakcja zostaje otwarta. Zapis do zmiennej tabelowej w tym samym miejscu się udaje, więc po ROLLBACK bufor trafia do dziennika.

04

Skąd bierze się błąd 3903 i jak go uniknąć?

Błąd 3903 pojawia się, gdy bezwarunkowy ROLLBACK w bloku CATCH wykonuje się przed BEGIN TRANSACTION, bo transakcji jeszcze nie ma. Przesłania on oryginalny błąd 8134. Warunek IF XACT_STATE() <> 0 przed ROLLBACK usuwa problem: wołający dostaje 8134, a w dzienniku powstaje wpis.

05

Dlaczego brak średnika przed THROW zmienia błąd na 6401?

THROW wymaga średnika w poprzedniej instrukcji. Bez niego ROLLBACK TRANSACTION zostaje odczytane jako wycofanie transakcji o nazwie THROW, co daje błąd 6401, a transakcja pozostaje otwarta.

Słownik pojęć

Słownik pojęć

Pojęcia użyte w przykładzie z buforem błędów.

ZZmienna tabelowa
Zmienna typu table, deklarowana w procedurze. W przykładzie zachowuje wiersze po ROLLBACK i pełni rolę bufora opisu kroków transakcji.
TTabela tymczasowa
Tabela tworzona poleceniem CREATE TABLE z nazwą zaczynającą się od znaku #. W przykładzie wiersz wstawiony w transakcji znika po ROLLBACK.
RROLLBACK
Polecenie wycofujące transakcję. W przykładzie cofa wiersz w tabeli Dane i wpisy zapisane do dziennika przed nim, ale nie rusza wierszy zmiennej tabelowej.
XXACT_STATE
Funkcja zwracająca stan transakcji: 0 oznacza brak transakcji, a -1 transakcję nieodwołalną. W przykładzie warunek na tę funkcję chroni ROLLBACK przed błędem 3903.
TTHROW
Instrukcja zgłaszająca błąd, a bez parametrów w bloku CATCH rzucająca dalej oryginalny. Wymaga średnika w poprzedniej instrukcji, inaczej pojawia się błąd 6401.
BBufor błędów
Zmienna tabelowa zbierająca opis przebiegu transakcji. W przykładzie blok CATCH przepisuje ją do dziennika po ROLLBACK, razem z numerem błędu.
XXACT_ABORT
Opcja sesji, przy której błąd wykonania przerywa transakcję. W przykładzie ustawia stan transakcji na -1, a zapis do dziennika przed ROLLBACK daje wtedy błąd 3930.
TTransakcja nieodwołalna
Transakcja w stanie XACT_STATE() równym -1, którą można tylko wycofać. W tym stanie zapis do tabeli dziennika kończy się błędem 3930, a zapis do zmiennej tabelowej się udaje.

Zobacz to w praktyce

Sprawdź, jak systemy SoftwareStudio oparte na MS SQL Server wspierają ten obszar Twojej firmy.