Czytałem MSDN o TRY...CATCH
i XACT_STATE
.
Ma następujący przykład, który wykorzystuje XACT_STATE
w CATCH
bloku TRY…CATCH
konstruktu do ustalenia, czy transakcja zostanie zatwierdzona, czy wycofana:
USE AdventureWorks2012;
GO
-- SET XACT_ABORT ON will render the transaction uncommittable
-- when the constraint violation occurs.
SET XACT_ABORT ON;
BEGIN TRY
BEGIN TRANSACTION;
-- A FOREIGN KEY constraint exists on this table. This
-- statement will generate a constraint violation error.
DELETE FROM Production.Product
WHERE ProductID = 980;
-- If the delete operation succeeds, commit the transaction. The CATCH
-- block will not execute.
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
-- Test XACT_STATE for 0, 1, or -1.
-- If 1, the transaction is committable.
-- If -1, the transaction is uncommittable and should
-- be rolled back.
-- XACT_STATE = 0 means there is no transaction and
-- a commit or rollback operation would generate an error.
-- Test whether the transaction is uncommittable.
IF (XACT_STATE()) = -1
BEGIN
PRINT 'The transaction is in an uncommittable state.' +
' Rolling back transaction.'
ROLLBACK TRANSACTION;
END;
-- Test whether the transaction is active and valid.
IF (XACT_STATE()) = 1
BEGIN
PRINT 'The transaction is committable.' +
' Committing transaction.'
COMMIT TRANSACTION;
END;
END CATCH;
GO
Nie rozumiem tylko, dlaczego powinienem się przejmować i sprawdzać, jakie XACT_STATE
zwroty?
Pamiętaj, że w przykładzie XACT_ABORT
ustawiono flagę ON
.
Jeśli wewnątrz TRY
bloku wystąpi wystarczająco poważny błąd , sterowanie przejdzie do CATCH
. Więc jeśli jestem w środku CATCH
, wiem, że transakcja miała problem i naprawdę jedyną rozsądną rzeczą do zrobienia w tym przypadku jest wycofanie go, prawda?
Ale ten przykład z MSDN sugeruje, że mogą zdarzyć się przypadki przekazania kontroli CATCH
i nadal sensowne jest zatwierdzenie transakcji. Czy ktoś mógłby podać praktyczny przykład, kiedy może się to zdarzyć, kiedy ma to sens?
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
.
Rozumiem, że artykuł MSDN about SET XACT_ABORT
zawiera przykład, w którym niektóre instrukcje wewnątrz transakcji są wykonywane pomyślnie, a inne nie, gdy XACT_ABORT
jest ustawiony na OFF
. Ale SET XACT_ABORT ON
jak to się dzieje, że XACT_STATE()
zwraca 1 w CATCH
bloku?
Początkowo napisałbym ten kod w następujący sposób:
USE AdventureWorks2012;
GO
-- SET XACT_ABORT ON will render the transaction uncommittable
-- when the constraint violation occurs.
SET XACT_ABORT ON;
BEGIN TRY
BEGIN TRANSACTION;
-- A FOREIGN KEY constraint exists on this table. This
-- statement will generate a constraint violation error.
DELETE FROM Production.Product
WHERE ProductID = 980;
-- If the delete operation succeeds, commit the transaction. The CATCH
-- block will not execute.
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
-- Some severe problem with the transaction
PRINT 'Rolling back transaction.';
ROLLBACK TRANSACTION;
END CATCH;
GO
Biorąc pod uwagę odpowiedź Maxa Vernona, napisałbym taki kod. Pokazał, że przed próbą warto sprawdzić, czy istnieje aktywna transakcja ROLLBACK
. Mimo to, ze SET XACT_ABORT ON
w CATCH
bloku może być albo skazane transakcji lub transakcji w ogóle nie ma. W każdym razie nie ma nic do zrobienia COMMIT
. Czy się mylę?
USE AdventureWorks2012;
GO
-- SET XACT_ABORT ON will render the transaction uncommittable
-- when the constraint violation occurs.
SET XACT_ABORT ON;
BEGIN TRY
BEGIN TRANSACTION;
-- A FOREIGN KEY constraint exists on this table. This
-- statement will generate a constraint violation error.
DELETE FROM Production.Product
WHERE ProductID = 980;
-- If the delete operation succeeds, commit the transaction. The CATCH
-- block will not execute.
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
-- Some severe problem with the transaction
IF (XACT_STATE()) <> 0
BEGIN
-- There is still an active transaction that should be rolled back
PRINT 'Rolling back transaction.';
ROLLBACK TRANSACTION;
END;
END CATCH;
GO
źródło
XACT_ABORT
naON
lubOFF
.TL; DR / Streszczenie: W tej części pytania:
Zrobiłem sporo badań na ten temat i teraz nie mogę znaleźć żadnych przypadków, w których
XACT_STATE()
powraca1
wewnątrzCATCH
bloku, kiedy@@TRANCOUNT > 0
i mienia sesjiXACT_ABORT
jestON
. I faktycznie, zgodnie z bieżącą stroną MSDN dla SET XACT_ABORT :To stwierdzenie wydaje się być zgodne z twoją spekulacją i moimi ustaleniami.
To prawda, ale instrukcje w tym przykładzie nie znajdują się w
TRY
bloku. Te same stwierdzenia w obrębieTRY
bloku nadal uniemożliwić wykonanie wszelkich stwierdzeń po jednym, który spowodował błąd, ale przy założeniu, żeXACT_ABORT
jestOFF
, gdy sterowanie jest przekazywane doCATCH
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śliXACT_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
do0
, 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ącTRY...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ącXACT_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 tejSET XACT_ABORT
dokumentacji).Myślę, że uzasadnione jest stwierdzenie, że:
TRY...CATCH
konstruktu w SQL Server 2005 spowodowało potrzebę nowego stanu Transakcji (tj. „niezaangażowania”) iXACT_STATE()
funkcji uzyskania tej informacji.XACT_STATE()
wCATCH
bloku ma sens tylko wtedy, gdy spełnione są oba poniższe warunki:XACT_ABORT
jestOFF
(inaczejXACT_STATE()
powinien zawsze wrócić-1
i@@TRANCOUNT
byłby wszystkim, czego potrzebujesz)CATCH
bloku lub gdzieś w łańcuchu, jeśli wywołania są zagnieżdżone, co powoduje zmianę (aCOMMIT
nawet dowolną instrukcję DML, DDL itp.) Zamiast wykonywaniaROLLBACK
. (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.TRY...CATCH
konstruktu w SQL Server 2005 przeważnie utraciłoXACT_ABORT ON
właściwość sesji, ponieważ zapewnia większy stopień kontroli nad transakcją (przynajmniej masz taką możliwośćCOMMIT
, pod warunkiem, żeXACT_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.XACT_STATE()
jest błędny lub w najlepszym przypadku wprowadzający w błąd, ponieważ pokazuje sprawdzanie,XACT_STATE() = 1
kiedyXACT_ABORT
jestON
.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, kiedyXACT_ABORT
jestON
), 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.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 a1
, oznacza to, że transakcja jest „dopuszczalna”, że masz wybór międzyCOMMIT
lubROLLBACK
. 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 wykonaniuBEGIN TRAN
tej czynności przekaże kontrolę do bloku CATCH, gdyXACT_ABORT
będzie,OFF
a XACT_STATE () zwróci1
. ALE, jeśli XACT_ABORT jestON
, wówczas transakcja jest „przerywana” (tj. Unieważniana) dla dowolnego błędu zerowego , a następnieXACT_STATE()
powraca-1
. W tym przypadku sprawdzanieXACT_STATE()
wewnątrzCATCH
bloku wydaje się bezużyteczne, ponieważ zawsze wydaje się, że zwraca „-1
kiedyXACT_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:Strona MSDN dla SET XACT_ABORT , w sekcji „Uwagi”, mówi:
i:
Strona MSDN dla POCZĄTKUJĄCEJ TRANSAKCJI w sekcji „Uwagi” mówi:
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łemXACT_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 / lubXACT_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ę doCATCH
bloku za pomocąXACT_STATE()
raportowania1
.Wypróbuj poniższe przykłady, aby zobaczyć wpływ
XACT_ABORT
na wartośćXACT_STATE()
:AKTUALIZACJA
Chociaż nie jest częścią pierwotnego pytania, w oparciu o te komentarze do tej odpowiedzi:
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 chceszROLLBACK
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. Poprzezusing (SqlTransaction _Tran = _Connection.BeginTransaction()) { ...
), a także używając błędu przerwania partii zamiast tylko instrukcji błąd przerwania, i stwierdził, że:@@TRANCOUNT
nadal wynosi> 0.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źnyROLLBACK
, być może nie w bezpośrednimCATCH
bloku, ale przed końcem partii.TRY...CATCH
konstrukcji, kiedyXACT_ABORT
to jestOFF
, błędy, które zakończyłyby Transakcję automatycznie, gdyby wystąpiły pozaTRY
blokiem, takie jak błędy przerwania partii, cofną pracę, ale nie zakończą Tranasction, pozostawiając ją jako „niegodną”. Wydanie aROLLBACK
jest formalnością potrzebną do zamknięcia Transakcji, ale praca została już wycofana.XACT_ABORT
jestON
, 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 wCATCH
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 naSELECT
liście wraz zXACT_STATE()
, wówczasXACT_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:
XACT_STATE()
wCATCH
bloku, kiedyXACT_ABORT
jest,ON
ponieważ zawsze zwracana jest wartość-1
.XACT_STATE()
wCATCH
bloku, kiedyXACT_ABORT
jestOFF
bardziej sensowne, ponieważ zwracana wartość będzie miała przynajmniej pewną zmienność, ponieważ zwróci w1
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.COMMIT
wCATCH
bloku, a następnie sprawdzić wartośćXACT_STATE()
i należySET XACT_ABORT OFF;
.XACT_ABORT ON
wydaje się nie oferować żadnej korzyści w stosunku doTRY...CATCH
konstruktu.XACT_STATE()
zapewnia znaczącą korzyść w porównaniu z samym sprawdzaniem@@TRANCOUNT
.XACT_STATE()
powraca1
wCATCH
bloku, gdyXACT_ABORT
jestON
. Myślę, że to błąd w dokumentacji.XACT_ABORT ON
jest to kwestia sporna, ponieważ błąd występujący wTRY
bloku automatycznie przywróci zmiany.TRY...CATCH
Konstrukcja ma tę zaletę, w stosunkuXACT_ABORT ON
do nie automatycznie anulowanie całą transakcję, a tym samym umożliwiając przeprowadzenie transakcji (tak długo, jakXACT_STATE()
powraca1
), które zaangażowana (nawet, jeśli to jest krawędź litery).Przykład
XACT_STATE()
powrocie-1
gdyXACT_ABORT
jestOFF
: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):XACT_STATE()
nie zgłaszał oczekiwanych wartości, gdy są używane w wyzwalaczach lubINSERT...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 wSELECT
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:
Te stwierdzenia są niepoprawne, biorąc pod uwagę następujący przykład:
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:
Nie mogę jednak znaleźć powodu, by nie ufać
@@TRANCOUNT
. Poniższy test pokazuje, że@@TRANCOUNT
rzeczywiście zwraca1
transakcję automatycznego zatwierdzania: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.źródło
Programowanie defensywne wymaga pisania kodu, który obsługuje jak najwięcej znanych stanów, zmniejszając w ten sposób ryzyko błędów.
Sprawdzanie XACT_STATE () w celu ustalenia, czy wycofanie można wykonać, jest po prostu dobrą praktyką. Ślepa próba wycofania oznacza, że możesz przypadkowo spowodować błąd w TRY ...
Jednym ze sposobów, w jaki wycofywanie może się nie powieść w TRY ... byłby POŁOW, gdybyś wyraźnie nie rozpoczął transakcji. Kopiowanie i wklejanie bloków kodu może to łatwo powodować.
źródło
ROLLBACK
nie działałoby w środku,CATCH
a ty podałeś dobry przykład. Sądzę, że może również szybko stać się nieuporządkowany, jeśliTRY ... CATCH ... ROLLBACK
zaangażowane są zagnieżdżone transakcje i zagnieżdżone procedury składowane z ich własnymi .IF (XACT_STATE()) = 1 COMMIT TRANSACTION;
Jak możemy skończyć wCATCH
bloku z transakcją dopuszczalną? Nie odważyłbym się popełnić (możliwego) śmieci z wnętrzaCATCH
. Moje rozumowanie jest następujące: jeśli jesteśmy w środku,CATCH
coś poszło nie tak, nie mogę ufać stanowi bazy danych, więc lepiejROLLBACK
cokolwiek mamy.