TL; DR / Streszczenie: W tej części pytania:
Nie widzę przypadków, w których kontrolę można przekazać do wewnątrz CATCH
transakcji, która może zostać zatwierdzona, gdy XACT_ABORT
jest ustawiona naON
.
Zrobiłem sporo badań na ten temat i teraz nie mogę znaleźć żadnych przypadków, w których XACT_STATE()
powraca 1
wewnątrz CATCH
bloku, kiedy @@TRANCOUNT > 0
i mienia sesji XACT_ABORT
jest ON
. I faktycznie, zgodnie z bieżącą stroną MSDN dla SET XACT_ABORT :
Gdy opcja SET XACT_ABORT jest WŁĄCZONA, jeśli instrukcja Transact-SQL zgłosi błąd w czasie wykonywania, cała transakcja zostanie zakończona i wycofana.
To stwierdzenie wydaje się być zgodne z twoją spekulacją i moimi ustaleniami.
Artykuł MSDN about SET XACT_ABORT
zawiera przykład, w którym niektóre instrukcje wewnątrz transakcji wykonują się pomyślnie, a niektóre kończą się niepowodzeniem, gdy XACT_ABORT
jest ustawiony naOFF
To prawda, ale instrukcje w tym przykładzie nie znajdują się w TRY
bloku. Te same stwierdzenia w obrębie TRY
bloku nadal uniemożliwić wykonanie wszelkich stwierdzeń po jednym, który spowodował błąd, ale przy założeniu, że XACT_ABORT
jest OFF
, gdy sterowanie jest przekazywane do CATCH
bloku transakcji jest nadal fizycznie ważne, że wszystkie wcześniejsze zmiany doszło bez błędu i mogą zostać popełnione, jeśli takie jest pragnienie, lub mogą zostać wycofane. Z drugiej strony, jeśli XACT_ABORT
tak, ON
to wszelkie wcześniejsze zmiany są automatycznie wycofywane, a następnie masz możliwość: a) wydaniaROLLBACK
co jest w większości tylko akceptacją sytuacji, ponieważ transakcja została już wycofana minus zresetowanie @@TRANCOUNT
do 0
, lub b) pojawia się błąd. Nie ma wielkiego wyboru, prawda?
Jednym z prawdopodobnie ważnych szczegółów tej układanki, które nie są widoczne w tej dokumentacji, SET XACT_ABORT
jest to, że ta właściwość sesji, a nawet przykładowy kod, istnieje już od SQL Server 2000 (dokumentacja jest prawie identyczna między wersjami), wyprzedzając TRY...CATCH
konstrukcję, która była wprowadzony w SQL Server 2005. patrząc na tej dokumentacji i znów patrząc na przykład ( bezTRY...CATCH
), stosując XACT_ABORT ON
powoduje natychmiastowego wycofywania transakcji: nie ma stan transakcja „niemożliwy do zatwierdzenia” (proszę zauważyć, że nie ma wzmianki na wszystkie „nieprzekazywalne” stan transakcji w tej SET XACT_ABORT
dokumentacji).
Myślę, że uzasadnione jest stwierdzenie, że:
- wprowadzenie
TRY...CATCH
konstruktu w SQL Server 2005 spowodowało potrzebę nowego stanu Transakcji (tj. „niezaangażowania”) i XACT_STATE()
funkcji uzyskania tej informacji.
- sprawdzanie
XACT_STATE()
w CATCH
bloku ma sens tylko wtedy, gdy spełnione są oba poniższe warunki:
XACT_ABORT
jest OFF
(inaczej XACT_STATE()
powinien zawsze wrócić -1
i @@TRANCOUNT
byłby wszystkim, czego potrzebujesz)
- Masz logikę w
CATCH
bloku lub gdzieś w łańcuchu, jeśli wywołania są zagnieżdżone, co powoduje zmianę (a COMMIT
nawet dowolną instrukcję DML, DDL itp.) Zamiast wykonywania ROLLBACK
. (jest to bardzo nietypowy przypadek użycia) ** patrz uwaga na dole, w sekcji AKTUALIZACJA 3, dotycząca nieoficjalnej rekomendacji Microsoftu, by zawsze sprawdzać XACT_STATE()
zamiast tego @@TRANCOUNT
, i dlaczego testowanie pokazuje, że ich rozumowanie się nie sprawdza.
- wprowadzenie
TRY...CATCH
konstruktu w SQL Server 2005 przeważnie utraciło XACT_ABORT ON
właściwość sesji, ponieważ zapewnia większy stopień kontroli nad transakcją (przynajmniej masz taką możliwość COMMIT
, pod warunkiem, że XACT_STATE()
się nie zwraca -1
).
Innym sposobem spojrzenia na to jest przed SQL Server 2005 , XACT_ABORT ON
pod warunkiem łatwego i niezawodnego sposobu zatrzymania przetwarzania w przypadku wystąpienia błędu, w porównaniu do sprawdzania @@ERROR
po każdej instrukcji.
- Przykładowy kod dokumentacji
XACT_STATE()
jest błędny lub w najlepszym przypadku wprowadzający w błąd, ponieważ pokazuje sprawdzanie, XACT_STATE() = 1
kiedy XACT_ABORT
jest ON
.
Długa część ;-)
Tak, ten przykładowy kod w MSDN jest nieco mylący (patrz również: @@ TRANCOUNT (Cofanie) vs. XACT_STATE ) ;-). I wydaje mi się, że jest to mylące, ponieważ albo pokazuje coś, co nie ma sensu (z tego powodu, o który pytasz: czy możesz mieć nawet transakcję „do zatwierdzenia” w CATCH
bloku, kiedy XACT_ABORT
jest ON
), a nawet jeśli jest to możliwe, nadal koncentruje się na technicznej możliwości, której niewielu kiedykolwiek będzie chciało lub potrzebowało, i ignoruje powód, dla którego jest bardziej prawdopodobne, że będzie to potrzebne.
Jeśli w bloku TRY występuje wystarczająco poważny błąd, sterowanie przejdzie w tryb CATCH. Tak więc, jeśli jestem w POŁOWIE, wiem, że transakcja miała problem i naprawdę jedyną rozsądną rzeczą do zrobienia w tym przypadku jest wycofanie go, prawda?
Myślę, że to pomogłoby, gdybyśmy upewnili się, że jesteśmy na tej samej stronie, jeśli chodzi o to, co rozumie się przez niektóre słowa i pojęcia:
„wystarczająco poważny błąd”: Żeby było jasne, SPRÓBUJ ... ŁAP wychwyci większość błędów. Lista elementów, które nie zostaną przechwycone, znajduje się na tej połączonej stronie MSDN, w sekcji „Błędy, na które nie wpływa próbowanie… Konstruuj POŁOW”.
„jeśli jestem w POŁOWIE, wiem, że transakcja miała problem” ( dodaje się em phas ): Jeśli przez „transakcję” rozumiesz logiczną jednostkę pracy określoną przez ciebie poprzez grupowanie instrukcji w jawną transakcję, to prawdopodobnie tak. Myślę, że większość z nas ludzi z DB zgadza się, że wycofanie jest „jedyną rozsądną rzeczą do zrobienia”, ponieważ prawdopodobnie mamy podobny pogląd na to, w jaki sposób i dlaczego używamy jawnych transakcji i zastanawiamy się, jakie kroki powinny stworzyć jednostkę atomową pracy.
Ale jeśli masz na myśli rzeczywiste jednostki pracy, które są pogrupowane w jawną transakcję, to nie, nie wiesz, że sama transakcja miała problem. Wystarczy tylko wiedzieć, że oświadczenie wykonanie w określonym wyraźnie transakcji podniosła błąd. Ale może to nie być instrukcja DML lub DDL. I nawet gdyby była to instrukcja DML, sama transakcja może być nadal możliwa do zatwierdzenia.
Biorąc pod uwagę dwa powyższe punkty, prawdopodobnie powinniśmy rozróżnić transakcje, których „nie możesz” zatwierdzić, od transakcji, których „nie chcesz” dokonać.
Kiedy XACT_STATE()
zwraca a 1
, oznacza to, że transakcja jest „dopuszczalna”, że masz wybór między COMMIT
lub ROLLBACK
. Możesz tego nie chcieć , ale jeśli z jakiegoś trudnego do wymyślenia przykładu z jakiegoś powodu chciałeś, przynajmniej możesz, ponieważ niektóre części Transakcji zakończyły się powodzeniem.
Ale kiedy XACT_STATE()
zwraca a -1
, to naprawdę musisz, ROLLBACK
ponieważ pewna część Transakcji przeszła w zły stan. Teraz zgadzam się, że jeśli kontrola została przekazana do bloku CATCH, wtedy sensowne jest po prostu sprawdzenie @@TRANCOUNT
, ponieważ nawet jeśli mógłbyś dokonać Transakcji, dlaczego miałbyś chcieć?
Ale jeśli zauważysz na początku przykładu, ustawienie XACT_ABORT ON
zmian trochę się zmienia. Możesz mieć regularny błąd, po wykonaniu BEGIN TRAN
tej czynności przekaże kontrolę do bloku CATCH, gdy XACT_ABORT
będzie, OFF
a XACT_STATE () zwróci 1
. ALE, jeśli XACT_ABORT jest ON
, wówczas transakcja jest „przerywana” (tj. Unieważniana) dla dowolnego błędu zerowego , a następnie XACT_STATE()
powraca -1
. W tym przypadku sprawdzanie XACT_STATE()
wewnątrz CATCH
bloku wydaje się bezużyteczne, ponieważ zawsze wydaje się, że zwraca „ -1
kiedy XACT_ABORT
jest” ON
.
Więc po co to jest XACT_STATE()
? Oto niektóre wskazówki:
Strona MSDN dla TRY...CATCH
w sekcji „Niezatwierdzone transakcje i XACT_STATE” mówi:
Błąd, który zwykle kończy transakcję poza blokiem TRY, powoduje, że transakcja przechodzi w stan niezaangażowania, gdy błąd występuje w bloku TRY.
Strona MSDN dla SET XACT_ABORT , w sekcji „Uwagi”, mówi:
Gdy SET XACT_ABORT jest WYŁĄCZONY, w niektórych przypadkach tylko instrukcja Transact-SQL, która zgłosiła błąd, jest wycofywana, a transakcja jest kontynuowana.
i:
XACT_ABORT musi być włączony dla instrukcji modyfikacji danych w transakcji niejawnej lub jawnej z większością dostawców OLE DB, w tym SQL Server.
Strona MSDN dla POCZĄTKUJĄCEJ TRANSAKCJI w sekcji „Uwagi” mówi:
Transakcja lokalna rozpoczęta przez instrukcję BEGIN TRANSACTION zostaje eskalowana do transakcji rozproszonej, jeśli przed zatwierdzeniem lub wycofaniem instrukcji zostaną wykonane następujące działania:
- Wykonywana jest instrukcja INSERT, DELETE lub UPDATE, która odwołuje się do zdalnej tabeli na połączonym serwerze. Instrukcja INSERT, UPDATE lub DELETE kończy się niepowodzeniem, jeśli dostawca OLE DB użyty do uzyskania dostępu do połączonego serwera nie obsługuje interfejsu ITransactionJoin.
Wydaje się, że najbardziej odpowiednie zastosowanie znajduje się w kontekście instrukcji DML serwera połączonego. I wierzę, że sam na to wpadłem wiele lat temu. Nie pamiętam wszystkich szczegółów, ale miało to coś wspólnego z niedostępnością zdalnego serwera iz jakiegoś powodu ten błąd nie został złapany w bloku TRY i nigdy nie został wysłany do POŁOWU, więc tak się stało COMMIT, kiedy nie powinien. Oczywiście może to być problem polegający na tym, że nie XACT_ABORT
ustawiłem, ON
a nie nie sprawdziłem XACT_STATE()
, a być może jedno i drugie. Pamiętam, że przeczytałem coś, co mówiło, że jeśli używasz połączonych serwerów i / lub transakcji rozproszonych, to musiałeś użyć XACT_ABORT ON
i / lub XACT_STATE()
, ale wydaje mi się, że nie mogę teraz znaleźć tego dokumentu. Jeśli go znajdę, zaktualizuję ten link.
Mimo to próbowałem kilku rzeczy i nie jestem w stanie znaleźć scenariusza, który ma XACT_ABORT ON
i przekazuje kontrolę do CATCH
bloku za pomocą XACT_STATE()
raportowania 1
.
Wypróbuj poniższe przykłady, aby zobaczyć wpływ XACT_ABORT
na wartość XACT_STATE()
:
SET XACT_ABORT OFF;
BEGIN TRY
BEGIN TRAN;
SELECT 1/0 AS [DivideByZero]; -- error, yo!
COMMIT TRAN;
END TRY
BEGIN CATCH
SELECT @@TRANCOUNT AS [@@TRANCOUNT],
XACT_STATE() AS [XactState],
ERROR_MESSAGE() AS [ErrorMessage]
IF (@@TRANCOUNT > 0)
BEGIN
ROLLBACK;
END;
END CATCH;
GO ------------------------------------------------
SET XACT_ABORT ON;
BEGIN TRY
BEGIN TRAN;
SELECT 1/0 AS [DivideByZero]; -- error, yo!
COMMIT TRAN;
END TRY
BEGIN CATCH
SELECT @@TRANCOUNT AS [@@TRANCOUNT],
XACT_STATE() AS [XactState],
ERROR_MESSAGE() AS [ErrorMessage]
IF (@@TRANCOUNT > 0)
BEGIN
ROLLBACK;
END;
END CATCH;
GO ------------------------------------------------
SET XACT_ABORT ON;
BEGIN TRY
SELECT 1/0 AS [DivideByZero]; -- error, yo!
END TRY
BEGIN CATCH
SELECT @@TRANCOUNT AS [@@TRANCOUNT],
XACT_STATE() AS [XactState],
ERROR_MESSAGE() AS [ErrorMessage]
END CATCH;
AKTUALIZACJA
Chociaż nie jest częścią pierwotnego pytania, w oparciu o te komentarze do tej odpowiedzi:
Czytałem artykuły Erlanda na temat obsługi błędów i transakcji, w których twierdzi, że domyślnie XACT_ABORT
jest OFF
to spowodowane starszymi przyczynami i zwykle powinniśmy to ustawić ON
.
...
"... jeśli zastosujesz się do rekomendacji i uruchomisz z SET XACT_ABORT ON, transakcja zawsze będzie skazana."
Przed użyciem XACT_ABORT ON
wszędzie pytałbym: co dokładnie tutaj zyskuje? Nie uważam za konieczne robić tego i ogólnie zalecam, abyś korzystał z niego tylko w razie potrzeby. Czy chcesz ROLLBACK
może być uchwyt dość łatwo za pomocą szablonu pokazanego na @ Remusa za odpowiedź , lub ten, który używam od lat, który jest w zasadzie to samo, ale bez punktu zapisu, jak pokazano w tej odpowiedzi (co obsługuje zagnieżdżone połączenia):
Czy jesteśmy zobowiązani do obsługi transakcji w kodzie C #, a także w procedurze przechowywanej
AKTUALIZACJA 2
Zrobiłem trochę więcej testów, tym razem tworząc małą aplikację konsoli .NET, tworząc transakcję w warstwie aplikacji, przed wykonaniem jakichkolwiek SqlCommand
obiektów (np. Poprzez using (SqlTransaction _Tran = _Connection.BeginTransaction()) { ...
), a także używając błędu przerwania partii zamiast tylko instrukcji błąd przerwania, i stwierdził, że:
- „Niepospolita” transakcja to transakcja, która w większości została już wycofana (zmiany zostały cofnięte), ale
@@TRANCOUNT
nadal wynosi> 0.
- Gdy masz „niekomunikatywną” transakcję, nie możesz jej wystawić,
COMMIT
ponieważ spowoduje to wygenerowanie błędu i błąd, mówiąc, że transakcja jest „niewydana”. Nie możesz też go zignorować / nic nie robić, ponieważ błąd zostanie wygenerowany, gdy partia zakończy się, informując, że partia została zakończona z opóźnioną, nieprzewidzianą transakcją i zostanie wycofana (więc, hm, jeśli i tak zostanie automatycznie wycofana, po co zawracać sobie głowę zgłaszaniem błędu?). Więc musisz wydać wyraźny ROLLBACK
, być może nie w bezpośrednim CATCH
bloku, ale przed końcem partii.
- W
TRY...CATCH
konstrukcji, kiedy XACT_ABORT
to jest OFF
, błędy, które zakończyłyby Transakcję automatycznie, gdyby wystąpiły poza TRY
blokiem, takie jak błędy przerwania partii, cofną pracę, ale nie zakończą Tranasction, pozostawiając ją jako „niegodną”. Wydanie a ROLLBACK
jest formalnością potrzebną do zamknięcia Transakcji, ale praca została już wycofana.
- Kiedy
XACT_ABORT
jest ON
, większość błędów działa jako przerywanie partii, a zatem zachowują się tak, jak opisano w punkcie punktowym bezpośrednio powyżej (# 3).
XACT_STATE()
, przynajmniej w CATCH
bloku, wyświetli komunikat -1
o błędach przerywania partii, jeśli w czasie błędu istniała aktywna transakcja.
XACT_STATE()
czasami wraca, 1
nawet gdy nie ma aktywnej Transakcji. Jeśli @@SPID
(między innymi) jest na SELECT
liście wraz z XACT_STATE()
, wówczas XACT_STATE()
zwróci 1, gdy nie będzie aktywnej Transakcji. To zachowanie zaczęło się w SQL Server 2012 i istnieje w 2014, ale nie testowałem w 2016.
Mając na uwadze powyższe punkty:
- Biorąc pod uwagę punkty 4 i 5, ponieważ większość błędów (lub wszystkich?) Spowoduje, że transakcja stanie się „niekomfortowa”, wydaje się całkowicie bezcelowe sprawdzanie
XACT_STATE()
w CATCH
bloku, kiedy XACT_ABORT
jest, ON
ponieważ zawsze zwracana jest wartość -1
.
- Sprawdzanie
XACT_STATE()
w CATCH
bloku, kiedy XACT_ABORT
jest OFF
bardziej sensowne, ponieważ zwracana wartość będzie miała przynajmniej pewną zmienność, ponieważ zwróci w 1
przypadku błędów przerywania instrukcji. Jeśli jednak kodujesz jak większość z nas, to rozróżnienie nie ma znaczenia, ponieważ i tak będziesz dzwonić ROLLBACK
tylko z powodu wystąpienia błędu.
- Jeśli okaże się, że sytuacja robi wydającemu nakaz
COMMIT
w CATCH
bloku, a następnie sprawdzić wartość XACT_STATE()
i należy SET XACT_ABORT OFF;
.
XACT_ABORT ON
wydaje się nie oferować żadnej korzyści w stosunku do TRY...CATCH
konstruktu.
- Nie mogę znaleźć scenariusza, w którym sprawdzanie
XACT_STATE()
zapewnia znaczącą korzyść w porównaniu z samym sprawdzaniem @@TRANCOUNT
.
- Nie mogę również znaleźć scenariusza, w którym
XACT_STATE()
powraca 1
w CATCH
bloku, gdy XACT_ABORT
jest ON
. Myślę, że to błąd w dokumentacji.
- Tak, możesz wycofać Transakcję, której nie rozpoczęto jawnie. W kontekście używania
XACT_ABORT ON
jest to kwestia sporna, ponieważ błąd występujący w TRY
bloku automatycznie przywróci zmiany.
TRY...CATCH
Konstrukcja ma tę zaletę, w stosunku XACT_ABORT ON
do nie automatycznie anulowanie całą transakcję, a tym samym umożliwiając przeprowadzenie transakcji (tak długo, jak XACT_STATE()
powraca 1
), które zaangażowana (nawet, jeśli to jest krawędź litery).
Przykład XACT_STATE()
powrocie -1
gdy XACT_ABORT
jest OFF
:
SET XACT_ABORT OFF;
BEGIN TRY
BEGIN TRAN;
SELECT CONVERT(INT, 'g') AS [ConversionError];
COMMIT TRAN;
END TRY
BEGIN CATCH
DECLARE @State INT;
SET @State = XACT_STATE();
SELECT @@TRANCOUNT AS [@@TRANCOUNT],
@State AS [XactState],
ERROR_MESSAGE() AS [ErrorMessage];
IF (@@TRANCOUNT > 0)
BEGIN
SELECT 'Rollin back...' AS [Transaction];
ROLLBACK;
END;
END CATCH;
AKTUALIZACJA 3
Powiązany z pozycją # 6 w sekcji UPDATE 2 (tj. Możliwa niepoprawna wartość zwrócona, XACT_STATE()
gdy nie ma aktywnej Transakcji):
- Dziwne / błędne zachowanie rozpoczęło się w SQL Server 2012 (do tej pory testowane przeciwko 2012 SP2 i 2014 SP1)
- W SQL Server w wersjach 2005, 2008 i 2008 R2
XACT_STATE()
nie zgłaszał oczekiwanych wartości, gdy są używane w wyzwalaczach lub INSERT...EXEC
scenariuszach: xact_state () nie można w wiarygodny sposób określić, czy transakcja jest skazana . Jednak w tych 3 wersjach (I tylko przetestowane na 2008 R2), XACT_STATE()
czy nie nieprawidłowo zgłosić 1
podczas stosowania w SELECT
z @@SPID
.
Zgłoszono błąd Connect dotyczący zachowania wymienionego tutaj, ale został on zamknięty jako „By Design”: XACT_STATE () może zwrócić niepoprawny stan transakcji w SQL 2012 . Test został jednak przeprowadzony przy wyborze DMV i stwierdzono, że w ten sposób naturalnie miałaby miejsce transakcja generowana przez system, przynajmniej dla niektórych DMV. W ostatecznej odpowiedzi państw członkowskich stwierdzono również, że:
Pamiętaj, że instrukcja JEŻELI, a także WYBIERZ bez FROM, nie rozpoczynają transakcji.
na przykład uruchomienie SELECT XACT_STATE (), jeśli nie masz wcześniejszej transakcji, zwróci 0.
Te stwierdzenia są niepoprawne, biorąc pod uwagę następujący przykład:
SELECT @@TRANCOUNT AS [TRANCOUNT], XACT_STATE() AS [XACT_STATE], @@SPID AS [SPID];
GO
DECLARE @SPID INT;
SET @SPID = @@SPID;
SELECT @@TRANCOUNT AS [TRANCOUNT], XACT_STATE() AS [XACT_STATE], @SPID AS [SPID];
GO
Dlatego nowy błąd Connect:
XACT_STATE () zwraca 1, gdy jest używany w SELECT z niektórymi zmiennymi systemowymi, ale bez klauzuli FROM
UWAGA: w „XACT_STATE () może zwrócić niepoprawny stan transakcji w SQL 2012” Połącz element połączony bezpośrednio powyżej, Microsoft (cóż, przedstawiciel) stwierdza:
@@ trancount zwraca liczbę instrukcji BEGIN TRAN. Nie jest to zatem wiarygodny wskaźnik tego, czy istnieje aktywna transakcja. XACT_STATE () zwraca również 1, jeśli istnieje aktywna transakcja automatycznego zatwierdzania, a zatem jest bardziej wiarygodnym wskaźnikiem tego, czy istnieje aktywna transakcja.
Nie mogę jednak znaleźć powodu, by nie ufać @@TRANCOUNT
. Poniższy test pokazuje, że @@TRANCOUNT
rzeczywiście zwraca 1
transakcję automatycznego zatwierdzania:
--- begin setup
GO
CREATE PROCEDURE #TransactionInfo AS
SET NOCOUNT ON;
SELECT @@TRANCOUNT AS [TranCount],
XACT_STATE() AS [XactState];
GO
--- end setup
DECLARE @Test TABLE (TranCount INT, XactState INT);
SELECT * FROM @Test; -- no rows
EXEC #TransactionInfo; -- 0 for both fields
INSERT INTO @Test (TranCount, XactState)
EXEC #TransactionInfo;
SELECT * FROM @Test; -- 1 row; 1 for both fields
Testowałem również na prawdziwym stole z wyzwalaczem i @@TRANCOUNT
wewnątrz wyzwalacza dokładnie raportowałem, 1
mimo że nie rozpoczęto żadnej wyraźnej transakcji.
XACT_ABORT
naON
lubOFF
.