Transakcja zagnieżdżona to transakcja rozpoczęta wewnątrz innej — SQL Server śledzi jej poziom licznikiem @@TRANCOUNT.
Jak SQL Server rozumie transakcję zagnieżdżoną
W SQL Server transakcja zagnieżdżona powstaje wtedy, gdy kod wykona kolejne polecenie BEGIN TRANSACTION w czasie, gdy transakcja nadrzędna jest już aktywna. Poziom zagnieżenia można odczytać za pomocą globalnej zmiennej systemowej @@TRANCOUNT. Jeżeli nie ma aktywnej transakcji, wartość wynosi zero. Pierwsze BEGIN TRANSACTION zwiększa ją do jeden, a każde następne rozpoczęcie transakcji zwiększa licznik o kolejny poziom.
Ten licznik opisuje poziom otwarcia transakcji, ale nie oznacza, że SQL Server tworzy niezależny punkt zatwierdzenia dla każdego zagnieżonego poziomu. W praktyce wszystkie poziomy należą do jednego kontekstu transakcyjnego połączenia. Dopiero wykonanie zewnętrznego COMMIT TRANSACTION, które zmniejszy @@TRANCOUNT do zera, zatwierdza całą transakcję. Wewnętrzne zatwierdzenia jedynie zmniejszają licznik i nie zapisują części operacji niezależnie od transakcji nadrzędnej.
Różnica między COMMIT a ROLLBACK
W SQL Server każde poprawne wykonanie COMMIT TRANSACTION zmniejsza wartość @@TRANCOUNT o jeden. Jeżeli licznik po wykonaniu polecenia nadal jest większy od zera, zmiany pozostają częścią aktywnej transakcji i mogą zostać wycofane przez kod znajdujący się wyżej w stosie wywołań. Z tego powodu procedura składowana, która rozpoczyna własną transakcję wewnątrz już aktywnej transakcji, nie może traktować swojego COMMIT jako ostatecznego zatwierdzenia.
Zachowanie ROLLBACK TRANSACTION bez nazwy punktu zapisu jest inne. Wycofuje ono całą aktywną transakcję i ustawia @@TRANCOUNT na zero, także wtedy, gdy kod działał w kilku poziomach zagnieżenia. Wewnętrzna procedura może więc nieumyślnie anulować pracę rozpoczętą przez procedurę nadrzędną. Samo późniejsze wykonanie COMMIT nie odtworzy wycofanych zmian. Próba zatwierdzenia po takim pełnym wycofaniu może zakończyć się błędem, ponieważ dla danego połączenia nie będzie już aktywnej transakcji.
Do częściowego wycofania operacji służy SAVE TRANSACTION, które tworzy nazwany punkt zapisu wewnątrz aktywnej transakcji. Polecenie ROLLBACK TRANSACTION nazwa_punktu cofa zmiany wykonane po tym punkcie, ale nie kończy całej transakcji i nie zmniejsza @@TRANCOUNT. Punkt zapisu nie jest jednak osobną transakcją ani niezależnym zatwierdzeniem. Jeśli transakcja jest oznaczona jako niemożliwa do zatwierdzenia, częściowy rollback może nie być dostępny.
Procedury składowane, błędy i stan XACT_STATE
Bezpieczna procedura składowana musi rozróżnić sytuację, w której sama rozpoczęła transakcję, od sytuacji, w której została wywołana już wewnątrz transakcji nadrzędnej. Przed wykonaniem BEGIN TRANSACTION można zapamiętać początkową wartość @@TRANCOUNT. Jeżeli początkowo wynosiła zero, procedura może rozpocząć transakcję i po sukcesie wykonać końcowy COMMIT. Jeżeli transakcja już istniała, procedura powinna pozostawić jej ostateczne zatwierdzenie kodowi wywołującemu, a w razie potrzeby użyć punktu zapisu.
W obsłudze błędów znaczenie ma także funkcja XACT_STATE(). Zwraca ona zero, gdy nie ma aktywnej transakcji, jeden, gdy aktywna transakcja może zostać zatwierdzona, oraz minus jeden, gdy transakcja jest aktywna, ale została oznaczona jako niezatwierdzalna. W ostatnim przypadku możliwe jest wyłącznie pełne ROLLBACK TRANSACTION. Kod obsługi błędu nie powinien wtedy próbować wykonać COMMIT ani zakładać, że rollback do punktu zapisu wystarczy.
Wzorzec oparty na TRY...CATCH powinien uwzględniać zarówno liczbę transakcji przed wejściem do procedury, jak i stan zwrócony przez XACT_STATE(). Po zakończeniu procedury wartość @@TRANCOUNT powinna odpowiadać poziomowi oczekiwanemu przez kod nadrzędny. Pozostawienie otwartej transakcji może blokować zasoby i powodować długie oczekiwanie innych zapytań, natomiast niekontrolowany rollback może anulować operacje niezwiązane bezpośrednio z błędem występującym w procedurze podrzędnej.
Kontrola przepływu zmian w SQL Server
Analiza problemu z transakcją zagnieżdżoną powinna obejmować kolejność poleceń wykonywanych na konkretnym połączeniu: BEGIN TRANSACTION, COMMIT, ROLLBACK oraz ewentualne punkty zapisu. Przydatne jest rejestrowanie wartości @@TRANCOUNT i XACT_STATE() przed rozpoczęciem operacji, po wywołaniu procedur oraz w bloku CATCH. Pozwala to ustalić, czy procedura pozostawiła transakcję otwartą, wykonała pełny rollback albo zwróciła sterowanie z transakcją oznaczoną jako niezatwierdzalna.
Warto również sprawdzić ustawienie SET XACT_ABORT, ponieważ przy wartości ON wystąpienie wielu błędów czasu wykonania powoduje automatyczne wycofanie całej transakcji. Nie każdy błąd zachowuje się identycznie, dlatego sam komunikat o wyjątku nie wystarcza do określenia stanu połączenia. Należy ocenić faktyczną wartość XACT_STATE(), liczbę otwartych poziomów oraz to, czy kod nadrzędny nadal oczekuje możliwości wykonania COMMIT.
Transakcje zagnieżdżone dotyczą mechanizmu silnika SQL Server i wymagają analizy kodu zapytań, procedur składowanych oraz obsługi błędów. Diagnostyka powinna koncentrować się na odtworzeniu kolejności operacji i sprawdzeniu, czy zakres transakcji odpowiada zakresowi logicznej operacji biznesowej. W środowisku, w którym występuje blokowanie, długie zapytania lub nieoczekiwane wycofywanie zmian, pomocne jest prześledzenie aktywnych sesji, ich transakcji i zależności między poleceniami.