Jaka jest korzyść z używania SET XACT_ABORT ON
w procedurze składowanej?
Jaka jest korzyść z używania SET XACT_ABORT ON
w procedurze składowanej?
Odpowiedzi:
SET XACT_ABORT ON
instruuje SQL Server, aby wycofał całą transakcję i przerwał partię, gdy wystąpi błąd w czasie wykonywania. Obejmuje to przypadki takie jak przekroczenie limitu czasu polecenia występujące w aplikacji klienckiej, a nie w samym SQL Server (co nie jest objęte XACT_ABORT OFF
ustawieniem domyślnym ).
Ponieważ przekroczenie limitu czasu zapytania pozostawi transakcję otwartą, SET XACT_ABORT ON
jest zalecane we wszystkich procedurach składowanych z jawnymi transakcjami (chyba że masz konkretny powód, aby zrobić inaczej), ponieważ konsekwencje wykonywania pracy przez aplikację na połączeniu z otwartą transakcją są katastrofalne.
Na blogu Dana Guzmana znajduje się naprawdę świetny przegląd ,
BEGIN TRY
- BEGIN CATCH
i ROLLBACK
z BEGIN CATCH
blokiem w Sql?
BEGIN TRY
- BEGIN CATCH
nie przechwytuje takich rzeczy, jak przekroczenie limitu czasu występującego w aplikacji klienckiej, a niektóre błędy SQL są również nie do wykrycia, pozostawiając otwartą transakcję, której się nie spodziewałeś.
Moim zdaniem SET XACT_ABORT ON stał się przestarzały przez dodanie BEGIN TRY / BEGIN CATCH w SQL 2k5. Przed blokami wyjątków w Transact-SQL obsługa błędów była naprawdę trudna, a niezrównoważone procedury były zbyt powszechne (procedury, które miały inny @@ TRANCOUNT na wyjściu w porównaniu z wejściem).
Dzięki dodaniu obsługi wyjątków języka Transact-SQL pisanie poprawnych procedur, które gwarantują prawidłowe zbilansowanie transakcji, jest znacznie łatwiejsze. Na przykład używam tego szablonu do obsługi wyjątków i transakcji zagnieżdżonych :
create procedure [usp_my_procedure_name]
as
begin
set nocount on;
declare @trancount int;
set @trancount = @@trancount;
begin try
if @trancount = 0
begin transaction
else
save transaction usp_my_procedure_name;
-- Do the actual work here
lbexit:
if @trancount = 0
commit;
end try
begin catch
declare @error int, @message varchar(4000), @xstate int;
select @error = ERROR_NUMBER(), @message = ERROR_MESSAGE(), @xstate = XACT_STATE();
if @xstate = -1
rollback;
if @xstate = 1 and @trancount = 0
rollback
if @xstate = 1 and @trancount > 0
rollback transaction usp_my_procedure_name;
raiserror ('usp_my_procedure_name: %d: %s', 16, 1, @error, @message) ;
end catch
end
go
Pozwala mi na pisanie atomowych procedur, które wycofują tylko swoją własną pracę w przypadku naprawialnych błędów.
Jednym z głównych problemów, z jakimi borykają się procedury Transact-SQL, jest czystość danych : czasami otrzymane parametry lub dane w tabelach są po prostu błędne, co skutkuje zduplikowanymi błędami klucza, błędami ograniczeń referencyjnych, błędami ograniczeń sprawdzania i tak dalej. W końcu to właśnie rola tych ograniczeń, jeśli te błędy czystości danych byłyby niemożliwe i wszystkie zostałyby przechwycone przez logikę biznesową, wszystkie ograniczenia byłyby przestarzałe (dramatyczna przesada dodana dla efektu). Jeśli XACT_ABORT jest ON, wszystkie te błędy powodują utratę całej transakcji, w przeciwieństwie do możliwości kodowania bloków wyjątków, które z wdziękiem obsługują wyjątek. Typowym przykładem jest próba wykonania INSERT i powrót do UPDATE w przypadku naruszenia PK.
Cytując MSDN :
Gdy SET XACT_ABORT jest ON, jeśli instrukcja Transact-SQL spowoduje błąd w czasie wykonywania, cała transakcja zostanie zakończona i wycofana. Gdy opcja SET XACT_ABORT jest wyłączona, w niektórych przypadkach tylko instrukcja języka Transact-SQL, która wywołała błąd, jest wycofywana, a transakcja kontynuuje przetwarzanie.
W praktyce oznacza to, że niektóre instrukcje mogą się nie powieść, pozostawiając transakcję „częściowo zakończoną” i wywołującemu może nie być żadnego znaku tego niepowodzenia.
Prosty przykład:
INSERT INTO t1 VALUES (1/0)
INSERT INTO t2 VALUES (1/1)
SELECT 'Everything is fine'
Ten kod zostałby wykonany „pomyślnie” przy wyłączonym XACT_ABORT i zakończyłby się błędem przy włączonym XACT_ABORT („INSERT INTO t2” nie zostanie wykonany, a aplikacja kliencka zgłosi wyjątek).
Bardziej elastycznym podejściem jest sprawdzenie @@ ERROR po każdej instrukcji (stara szkoła) lub użycie bloków TRY ... CATCH (MSSQL2005 +). Osobiście wolę ustawić XACT_ABORT ON, gdy nie ma powodu do zaawansowanej obsługi błędów.
Jeśli chodzi o timeouty klientów i użycie XACT_ABORT do ich obsługi, moim zdaniem istnieje co najmniej jeden bardzo dobry powód, aby mieć limity czasu w API klienta, takie jak SqlClient, a mianowicie chronić kod aplikacji klienta przed zakleszczeniami występującymi w kodzie serwera SQL. W tym przypadku kod klienta nie ma błędu, ale musi chronić go przed zablokowaniem na zawsze, czekając na zakończenie polecenia na serwerze. I odwrotnie, jeśli muszą istnieć limity czasu klienta, aby chronić kod klienta, XACT_ABORT ON musi chronić kod serwera przed przerwaniami klienta, na wypadek gdyby kod serwera trwał dłużej, niż klient jest skłonny czekać.