Sprawdzanie stanu @@TRANCOUNT pozwala ustalić liczbę aktywnych, zagnieżdżonych transakcji w bieżącej sesji.
Co pokazuje @@TRANCOUNT w SQL Server
@@TRANCOUNT jest globalną zmienną systemową programu Microsoft SQL Server, która zwraca liczbę aktywnych poziomów transakcji w bieżącej sesji. Wynik dotyczy wyłącznie aktualnego połączenia z serwerem, dlatego wartość odczytana w jednym połączeniu nie opisuje stanu transakcji w innych sesjach. Przy wartości równej zero sesja nie ma aktywnej transakcji. Wartość dodatnia oznacza, że kod wykonał co najmniej jedno polecenie BEGIN TRANSACTION albo działa w transakcji rozpoczętej wcześniej przez wywołujący kod.
W SQL Server zagnieżdżanie transakcji jest odwzorowane przede wszystkim jako licznik, a nie jako niezależny zestaw transakcji, które można dowolnie zatwierdzać osobno. Każde wykonanie BEGIN TRANSACTION zwiększa @@TRANCOUNT o jeden. Wykonanie COMMIT TRANSACTION zmniejsza tę wartość o jeden, ale dopiero zatwierdzenie poziomu zewnętrznego faktycznie kończy transakcję i utrwala jej zmiany. Sam odczyt licznika nie informuje natomiast, czy transakcja jest możliwa do zatwierdzenia po wystąpieniu błędu. Do tego celu służy między innymi XACT_STATE().
Jak BEGIN, COMMIT i ROLLBACK zmieniają licznik
Najprostszy scenariusz rozpoczyna się od wartości @@TRANCOUNT = 0. Po BEGIN TRANSACTION licznik wynosi jeden. Kolejne BEGIN TRANSACTION zwiększają go dalej, na przykład do dwóch lub trzech. Odpowiednia liczba poleceń COMMIT TRANSACTION zmniejsza licznik stopniowo. Dopóki wynik jest większy od zera, SQL Server nadal uważa transakcję za otwartą. Jeżeli procedura składowana rozpocznie transakcję, a następnie wykona tylko jeden COMMIT przy wejściowym liczniku równym jeden, pozostawi licznik na poziomie jeden, zamiast zakończyć całą transakcję wywołującego.
ROLLBACK TRANSACTION bez wskazania punktu zapisu cofa całą bieżącą transakcję i ustawia @@TRANCOUNT na zero. To zachowanie ma znaczenie przy obsłudze błędów w procedurach wywoływanych w ramach transakcji należącej do kodu nadrzędnego: pełny rollback może zakończyć również transakcję rozpoczętą przed wejściem do procedury. Inaczej działa ROLLBACK TRANSACTION nazwa_punktu_zapisu. Cofnięcie do punktu zapisu nie zmniejsza wartości @@TRANCOUNT, ponieważ punkt zapisu nie tworzy osobnej transakcji. Po takim rollbacku sesja nadal pozostaje w transakcji.
Kontrola granic transakcji w procedurach i obsłudze błędów
Procedura składowana, która ma być bezpieczna dla kodu wywołującego, powinna rozróżniać transakcję własną od transakcji odziedziczonej. Przed rozpoczęciem pracy można zapisać początkową wartość @@TRANCOUNT. Jeżeli wynosi zero, procedura może rozpocząć własną transakcję i po pomyślnym wykonaniu zatwierdzić ją. Jeżeli wartość jest dodatnia, procedura działa już w kontekście transakcji zewnętrznej. W takim przypadku często stosuje się SAVE TRANSACTION, aby w razie błędu cofnąć zmiany wykonane przez procedurę do punktu zapisu, bez wykonywania pełnego rollbacku transakcji właściciela.
W bloku TRY...CATCH sam @@TRANCOUNT nie wystarcza do podjęcia decyzji o zatwierdzeniu lub wycofaniu zmian. XACT_STATE() zwraca stan transakcji: zero oznacza brak transakcji, jeden oznacza transakcję aktywną i możliwą do zatwierdzenia, a minus jeden oznacza transakcję niezatwierdzalną, w której dozwolony jest tylko pełny ROLLBACK. Po błędzie kod powinien więc sprawdzić oba mechanizmy oraz zachować równowagę liczby rozpoczętych i zakończonych poziomów. Pozostawienie dodatniej wartości licznika po wyjściu z procedury może powodować blokady, opóźnione zwalnianie zasobów i błędy przy późniejszym użyciu tego samego połączenia.
Odczyt @@TRANCOUNT podczas diagnozy problemu
Diagnostykę warto rozpocząć od odczytania SELECT @@TRANCOUNT AS TranCount, XACT_STATE() AS XactState; w tym samym połączeniu, w którym wystąpił problem. Wynik należy rejestrować przed rozpoczęciem transakcji, po operacjach zapisu, po obsłudze wyjątku oraz przed zakończeniem procedury. Taki pomiar pozwala ustalić, czy kod otworzył transakcję, czy wykonał zbyt mało poleceń COMMIT, czy też pełny rollback zakończył transakcję wcześniej, niż zakładał dalszy kod. Nie wolno porównywać wartości pochodzących z różnych sesji, ponieważ każda sesja ma własny kontekst transakcyjny.