W jakich przypadkach transakcja może zostać zatwierdzona z bloku CATCH, gdy XACT_ABORT jest ustawiony na ON?

13

Czytałem MSDN o TRY...CATCHi XACT_STATE.

Ma następujący przykład, który wykorzystuje XACT_STATEw CATCHbloku TRY…CATCHkonstruktu 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_STATEzwroty?

Pamiętaj, że w przykładzie XACT_ABORTustawiono flagę ON.

Jeśli wewnątrz TRYbloku 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 CATCHi 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 CATCHtransakcji, która może zostać zatwierdzona, gdy XACT_ABORTjest ustawiona naON .

Rozumiem, że artykuł MSDN about SET XACT_ABORTzawiera przykład, w którym niektóre instrukcje wewnątrz transakcji są wykonywane pomyślnie, a inne nie, gdy XACT_ABORTjest ustawiony na OFF. Ale SET XACT_ABORT ONjak to się dzieje, że XACT_STATE()zwraca 1 w CATCHbloku?

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 ONw CATCHbloku 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
Vladimir Baranov
źródło

Odpowiedzi:

8

Okazuje się, że transakcji nie można zatwierdzić z CATCHbloku, jeśli XACT_ABORTjest ustawiona na ON.

Przykład z MSDN jest nieco mylący, ponieważ kontrola sugeruje, że XACT_STATEw niektórych przypadkach może zwrócić 1, a COMMITtransakcja może być możliwa .

IF (XACT_STATE()) = 1
BEGIN
    PRINT 'The transaction is committable.' + 
          ' Committing transaction.'
    COMMIT TRANSACTION;   
END;

To nieprawda, XACT_STATEnigdy nie zwróci 1 wewnątrz CATCHbloku, jeśli XACT_ABORTjest ustawiony na ON.

Wygląda na to, że przykładowy kod MSDN miał przede wszystkim zilustrować użycie XACT_STATE()funkcji niezależnie od XACT_ABORTustawienia. Przykładowy kod wygląda na tyle ogólny, że działa zarówno z XACT_ABORTustawionymi na, jak ONi na OFF. Po prostu z XACT_ABORT = ONczekiem IF (XACT_STATE()) = 1staje się niepotrzebny.


Istnieje bardzo dobry szczegółowy zestaw artykułów na temat obsługi błędów i transakcji w programie SQL Server autorstwa Erlanda Sommarskoga. W części 2 - Klasyfikacja błędów przedstawia obszerną tabelę, w której zestawiono wszystkie klasy błędów i sposób ich obsługi przez program SQL Server oraz sposób TRY ... CATCHi XACT_ABORTzachowanie.

+-----------------------------+---------------------------++------------------------------+
|                             |     Without TRY-CATCH     ||        With TRY-CATCH        |
+-----------------------------+-------+-------+-----+-----++------------------+-----+-----+
|              SET XACT_ABORT |  OFF  |  ON   | OFF | ON  ||    ON or OFF     | OFF | ON  |
+-----------------------------+-------+-------+-----+-----++------------------+-----+-----+
| Class Name                  |    Aborts     |   Rolls   ||    Catchable     |   Dooms   |
|                             |               |   Back    ||                  |transaction|
+-----------------------------+-------+-------+-----+-----++------------------+-----+-----+
| Fatal errors                |  Connection   |    Yes    ||       No         |    n/a    |
| Batch-aborting              |     Batch     |    Yes    ||       Yes        |    Yes    |
| Batch-only aborting         |     Batch     | No  | Yes ||       Yes        | No  | Yes |
| Statement-terminating       | Stmnt | Batch | No  | Yes ||       Yes        | No  | Yes |
| Terminates nothing at all   |    Nothing    |    No     ||       Yes        | No  | Yes |
| Compilation: syntax errors  |  (Statement)  |    No     ||       Yes        | No  | Yes |
| Compilation: binding errors | Scope | Batch | No  | Yes || Outer scope only | No  | Yes |
| Compilation: optimisation   |     Batch     |    Yes    || Outer scope only |    Yes    |
| Attention signal            |     Batch     | No  | Yes ||       No         |    n/a    |
| Informational/warning msgs  |    Nothing    |    No     ||       No         |    n/a    |
| Uncatchable errors          |    Varying    |  Varying  ||       No         |    n/a    |
+-----------------------------+-------+-------+-----+-----++------------------+-----+-----+

Ostatnia kolumna w tabeli odpowiada na pytanie. Z TRY-CATCHi wraz XACT_ABORT ONz transakcją jest skazana na wszystkie możliwe przypadki.

Jedna uwaga poza zakresem pytania. Jak mówi Erland, to konsekwencja jest jednym z powodów, aby ustawić XACT_ABORTna ON:

Dałem już zalecenie, aby twoje procedury składowane zawierały polecenie SET XACT_ABORT, NOCOUNT ON. Jeśli spojrzysz na powyższą tabelę, zobaczysz, że XACT_ABORTw efekcie istnieje pewien wyższy poziom spójności. Na przykład transakcja jest zawsze skazana na zagładę. W dalszej części pokażę wiele przykładów, gdzie mogę ustawić XACT_ABORTaby OFF, dzięki czemu można uzyskać zrozumienie, dlaczego należy unikać to ustawienie domyślne.

Vladimir Baranov
źródło
7

Podejdę do tego inaczej. XACT_ABORT_ONjest młotem saneczkowym, możesz zastosować bardziej wyrafinowane podejście, zobacz Obsługa wyjątków i transakcje zagnieżdżone :

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

Podejście to cofnie, jeśli to możliwe, tylko pracę wykonaną wewnątrz bloku TRY i przywróci stan do stanu przed wejściem do bloku TRY. W ten sposób możesz wykonywać złożone operacje, takie jak iterowanie kursora, bez utraty całej pracy w przypadku błędu. Jedyną wadą jest to, że stosując punkty zapisu transakcji, nie można używać niczego, co jest niezgodne z punktami zapisu, np. Transakcji rozproszonych.

Remus Rusanu
źródło
Cenię swoją odpowiedź, ale nie chodzi o to czy naprawdę powinniśmy ustawić XACT_ABORTna ONlub OFF.
Vladimir Baranov
7

TL; DR / Streszczenie: W tej części pytania:

Nie widzę przypadków, w których kontrolę można przekazać do wewnątrz CATCHtransakcji, która może zostać zatwierdzona, gdy XACT_ABORTjest ustawiona naON .

Zrobiłem sporo badań na ten temat i teraz nie mogę znaleźć żadnych przypadków, w których XACT_STATE()powraca 1wewnątrz CATCHbloku, kiedy @@TRANCOUNT > 0 i mienia sesji XACT_ABORTjest 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_ABORTzawiera przykład, w którym niektóre instrukcje wewnątrz transakcji wykonują się pomyślnie, a niektóre kończą się niepowodzeniem, gdy XACT_ABORTjest ustawiony naOFF

To prawda, ale instrukcje w tym przykładzie nie znajdują się w TRYbloku. Te same stwierdzenia w obrębie TRYbloku nadal uniemożliwić wykonanie wszelkich stwierdzeń po jednym, który spowodował błąd, ale przy założeniu, że XACT_ABORTjest OFF, gdy sterowanie jest przekazywane do CATCHbloku 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_ABORTtak, ONto wszelkie wcześniejsze zmiany są automatycznie wycofywane, a następnie masz możliwość: a) wydaniaROLLBACKco jest w większości tylko akceptacją sytuacji, ponieważ transakcja została już wycofana minus zresetowanie @@TRANCOUNTdo 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_ABORTjest 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...CATCHkonstrukcję, 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 ONpowoduje 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_ABORTdokumentacji).

Myślę, że uzasadnione jest stwierdzenie, że:

  1. wprowadzenie TRY...CATCHkonstruktu w SQL Server 2005 spowodowało potrzebę nowego stanu Transakcji (tj. „niezaangażowania”) i XACT_STATE()funkcji uzyskania tej informacji.
  2. sprawdzanie XACT_STATE()w CATCHbloku ma sens tylko wtedy, gdy spełnione są oba poniższe warunki:
    1. XACT_ABORTjest OFF(inaczej XACT_STATE()powinien zawsze wrócić -1i @@TRANCOUNTbyłby wszystkim, czego potrzebujesz)
    2. Masz logikę w CATCHbloku lub gdzieś w łańcuchu, jeśli wywołania są zagnieżdżone, co powoduje zmianę (a COMMITnawet 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.
  3. wprowadzenie TRY...CATCHkonstruktu w SQL Server 2005 przeważnie utraciło XACT_ABORT ONwł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 ONpod warunkiem łatwego i niezawodnego sposobu zatrzymania przetwarzania w przypadku wystąpienia błędu, w porównaniu do sprawdzania @@ERRORpo każdej instrukcji.
  4. Przykładowy kod dokumentacji XACT_STATE()jest błędny lub w najlepszym przypadku wprowadzający w błąd, ponieważ pokazuje sprawdzanie, XACT_STATE() = 1kiedy XACT_ABORTjest 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 CATCHbloku, kiedy XACT_ABORTjest 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 COMMITlub 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, ROLLBACKponieważ 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 ONzmian trochę się zmienia. Możesz mieć regularny błąd, po wykonaniu BEGIN TRANtej czynności przekaże kontrolę do bloku CATCH, gdy XACT_ABORTbędzie, OFFa 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 CATCHbloku wydaje się bezużyteczne, ponieważ zawsze wydaje się, że zwraca „ -1kiedy XACT_ABORTjest” ON.

Więc po co to jest XACT_STATE()? Oto niektóre wskazówki:

  • Strona MSDN dla TRY...CATCHw 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_ABORTustawiłem, ONa 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 ONi / 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 ONi przekazuje kontrolę do CATCHbloku za pomocą XACT_STATE()raportowania 1.

Wypróbuj poniższe przykłady, aby zobaczyć wpływ XACT_ABORTna 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_ABORTjest OFFto 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 ONwszę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 ROLLBACKmoż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 SqlCommandobiektó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:

  1. „Niepospolita” transakcja to transakcja, która w większości została już wycofana (zmiany zostały cofnięte), ale @@TRANCOUNTnadal wynosi> 0.
  2. Gdy masz „niekomunikatywną” transakcję, nie możesz jej wystawić, COMMITponieważ 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 CATCHbloku, ale przed końcem partii.
  3. W TRY...CATCHkonstrukcji, kiedy XACT_ABORTto jest OFF, błędy, które zakończyłyby Transakcję automatycznie, gdyby wystąpiły poza TRYblokiem, takie jak błędy przerwania partii, cofną pracę, ale nie zakończą Tranasction, pozostawiając ją jako „niegodną”. Wydanie a ROLLBACKjest formalnością potrzebną do zamknięcia Transakcji, ale praca została już wycofana.
  4. Kiedy XACT_ABORTjest 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).
  5. XACT_STATE(), przynajmniej w CATCHbloku, wyświetli komunikat -1o błędach przerywania partii, jeśli w czasie błędu istniała aktywna transakcja.
  6. XACT_STATE()czasami wraca, 1nawet gdy nie ma aktywnej Transakcji. Jeśli @@SPID(między innymi) jest na SELECTliś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 CATCHbloku, kiedy XACT_ABORTjest, ONponieważ zawsze zwracana jest wartość -1.
  • Sprawdzanie XACT_STATE()w CATCHbloku, kiedy XACT_ABORTjest OFFbardziej sensowne, ponieważ zwracana wartość będzie miała przynajmniej pewną zmienność, ponieważ zwróci w 1przypadku 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ć ROLLBACKtylko z powodu wystąpienia błędu.
  • Jeśli okaże się, że sytuacja robi wydającemu nakaz COMMITw CATCHbloku, a następnie sprawdzić wartość XACT_STATE()i należy SET XACT_ABORT OFF;.
  • XACT_ABORT ONwydaje się nie oferować żadnej korzyści w stosunku do TRY...CATCHkonstruktu.
  • 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 1w CATCHbloku, gdy XACT_ABORTjest 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 ONjest to kwestia sporna, ponieważ błąd występujący w TRYbloku automatycznie przywróci zmiany.
  • TRY...CATCHKonstrukcja ma tę zaletę, w stosunku XACT_ABORT ONdo 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 -1gdy XACT_ABORTjest 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...EXECscenariuszach: 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ć 1podczas stosowania w SELECTz @@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 @@TRANCOUNTrzeczywiście zwraca 1transakcję 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 @@TRANCOUNTwewnątrz wyzwalacza dokładnie raportowałem, 1mimo że nie rozpoczęto żadnej wyraźnej transakcji.

Solomon Rutzky
źródło
4

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ć.

Max Vernon
źródło
Dziękuję za odpowiedź. Po prostu nie mogłem wymyślić przypadku, w którym proste ROLLBACKnie działałoby w środku, CATCHa ty podałeś dobry przykład. Sądzę, że może również szybko stać się nieuporządkowany, jeśli TRY ... CATCH ... ROLLBACKzaangażowane są zagnieżdżone transakcje i zagnieżdżone procedury składowane z ich własnymi .
Vladimir Baranov
Byłbym jednak wdzięczny, gdybyś mógł rozszerzyć swoją odpowiedź dotyczącą drugiej części - IF (XACT_STATE()) = 1 COMMIT TRANSACTION; Jak możemy skończyć w CATCHbloku z transakcją dopuszczalną? Nie odważyłbym się popełnić (możliwego) śmieci z wnętrza CATCH. Moje rozumowanie jest następujące: jeśli jesteśmy w środku, CATCHcoś poszło nie tak, nie mogę ufać stanowi bazy danych, więc lepiej ROLLBACKcokolwiek mamy.
Vladimir Baranov