2012-03-15 4 views
12

Mam procedurę składowaną, która musi ustawić punkt zapisu, aby w pewnych okolicznościach mógł cofnąć wszystko, co zrobił i zwrócił kod błędu dzwoniącemu lub zaakceptuj/zatwierdz i zwróć sukces rozmówcy. Ale potrzebuję go do pracy, czy dzwoniący już rozpoczął transakcję, czy nie. Doktor jest niezmiernie zagmatwany na ten temat. Oto, co moim zdaniem zadziała, ale nie jestem pewien wszystkich konsekwencji. To jest - Stored Procedure (SP) jest wywoływana przez innych. Nie wiem, czy rozpoczęli transakcję, czy nie ... Nawet jeśli wymagam od użytkowników rozpoczęcia transakcji, aby użyć mojego SP, nadal mam pytania dotyczące prawidłowego korzystania z usługi Save Points ...ZAPISZ TRANSAKCJĘ vs BEGIN TRANSAKCJĘ (SQL Server), jak ładnie zagnieździć transakcje

Moje SP przetestuje, czy transakcja jest w toku, a jeśli nie, uruchom ją przy pomocy BEGIN TRANSACTION. Jeśli transakcja jest już w toku, utworzy punkt zapisu z numerem SAVE TRANSACTION MySavePointName i zapisze fakt, że właśnie to zrobiłem.

Następnie, jeśli muszę wycofać moje zmiany, jeśli wcześniej zrobiłem BEGIN TRANSACTION, będę ROLLBACK TRANSACTION. Jeśli zrobiłem punkt zapisu, to będę ROLLBACK TRANSACTION MySavePointName. Ten scenariusz wydaje się działać świetnie.

Tutaj się trochę zmieszam - jeśli chcę zachować pracę, którą wykonałem, jeśli rozpocznę transakcję, wykonam COMMIT TRANSACTION. Ale jeśli stworzyłem punkt zapisu? Próbowałem COMMIT TRANSACTION MySavePointName, ale wtedy rozmówca próbuje popełnić swoją transakcję i dostaje błąd:

The COMMIT TRANSACTION request has no corresponding BEGIN TRANSACTION.

Więc zastanawiam następnie - punkt zaoszczędzić można wycofać (to działa: ROLLBACK TRANSACTION MySavePointName nie przywróci rozmówcy transakcja). Ale może nigdy nie trzeba tego "popełniać"? Pozostaje tam tylko na wypadek, gdybyś musiał wrócić do niego, ale odejdzie, gdy pierwotna transakcja zostanie zatwierdzona (lub wycofana)?

Jeśli istnieje "lepszy" sposób "zagnieżdżenia" transakcji, proszę rzucić trochę światła. Nie zorientowałem się, jak zagnieżdżać z BEGIN TRANSACTION, ale tylko wycofać lub zatwierdzić moją wewnętrzną transakcję. Wydaje się, że ROLLBACK zawsze wycofa się do najwyższej transakcji, podczas gdy COMMIT po prostu dekretyje @@trancount.

+0

Twój znalezisko może być warta opublikowania jako odpowiedź. –

+0

@Andriy Ok, to właśnie zrobiłem - usunąłem moją edycję i użyłem jej zamiast odpowiedzi. Dzięki. –

Odpowiedz

19

wierzę ja wzorzysty to wszystko teraz, więc będę odpowiedzieć na moje własne pytanie ...

mam nawet blogu moje wyniki, jeśli chcesz więcej szczegółów na http://geekswithblogs.net/bbiales/archive/2012/03/15/how-to-nest-transactions-nicely---quotbegin-transactionquot-vs-quotsave.aspx

więc moja SP zaczyna się od czegoś takiego, aby rozpocząć nową transakcję, jeżeli nie ma, ale korzystać z punktu zapisu, jeśli jest już w toku:

DECLARE @startingTranCount int 
SET @startingTranCount = @@TRANCOUNT 

IF @startingTranCount > 0 
    SAVE TRANSACTION mySavePointName 
ELSE 
    BEGIN TRANSACTION 
-- … 

Wtedy, gdy jest gotowy, aby zatwierdzić zmiany, wystarczy popełnić jeśli Sami zawarliśmy transakcję:

IF @startingTranCount = 0 
    COMMIT TRANSACTION 

I wreszcie, aby przywrócić tylko zmiany tej pory:

-- Roll back changes... 
IF @startingTranCount > 0 
    ROLLBACK TRANSACTION MySavePointName 
ELSE 
    ROLLBACK TRANSACTION 
+0

jakikolwiek powód, aby nie uruchamiać nowej zagnieżdżonej transakcji po 'zapisaniu transakcji'? – sotn

+0

Poniżej znajduje się link do artykułu o transakcjach zagnieżdżania. Jeśli transakcja zagnieżdżona zostanie zatwierdzona, obniża liczbę zagnieżdżonych transakcji, ale NIE wykonuje żadnych zobowiązań. Jeśli więc zewnętrzna transakcja zostanie zatwierdzona, wszystko jest, jeśli zostanie wycofana, wszystko zostanie wycofane, w tym także "zobowiązana". Jeśli zagnieżdżona transakcja wykonuje operację wycofywania, wykonuje ona OBU sam siebie i transakcje zewnętrzne. Jeśli TYLKO chcesz wycofać swoją transakcję bez wpływu na transakcje, w których już jesteś, skorzystaj z techniki opisanej tutaj. –

+0

Oto artykuł na temat transakcji zagnieżdżonych: https://technet.microsoft.com/en-us/library/ms189336%28v=sql.105%29.aspx?f=255&MSPPError=-2147217396 –

10

Rozszerzanie Brian B's answer.

Dzięki temu nazwa punktu zapisu jest unikatowa i wykorzystuje nowe funkcje TRY/CATCH/ThROW programu SQL Server 2012.

DECLARE @mark CHAR(32) = replace(newid(), '-', ''); 
DECLARE @trans INT = @@TRANCOUNT; 

IF @trans = 0 
    BEGIN TRANSACTION @mark; 
ELSE 
    SAVE TRANSACTION @mark; 

BEGIN TRY 
    -- do work here 

    IF @trans = 0 
     COMMIT TRANSACTION @mark; 
END TRY 
BEGIN CATCH 
    IF xact_state() = 1 OR (@trans = 0 AND xact_state() <> 0) ROLLBACK TRANSACTION @mark; 
    THROW; 
END CATCH 
+0

Nie byłem z powrotem do tego posta, ale uwielbiam szablon, który można ponownie użyć dosłowno w następnym zapytaniu. Niezłe ulepszenie. –

+0

Widziałem na sugestie MSDN, aby umieścić oświadczenie rozpoczęcia transakcji wewnątrz bloku try. Czy zrobienie tego w ten sposób powoduje pewne problemy lub jest całkowicie bezpieczne? –

+0

Jestem zdezorientowany jeśli chodzi o instrukcję if w bloku catch. Dlaczego wycofywanie transakcji, jeśli stan xact = -1. Czy nie powinno to być <> - 1? –

2

Użyłem tego rodzaju menedżera transakcji w moim procedury przechowywane:

CREATE PROCEDURE Ardi_Sample_Test 
     @InputCandidateID INT 
    AS 
     DECLARE @TranCounter INT; 
     SET @TranCounter = @@TRANCOUNT; 
     IF @TranCounter > 0 
      SAVE TRANSACTION ProcedureSave; 
     ELSE 
      BEGIN TRANSACTION; 
     BEGIN TRY 

      /* 
      <Your Code> 
      */ 

      IF @TranCounter = 0 
       COMMIT TRANSACTION; 
     END TRY 
     BEGIN CATCH 
      IF @TranCounter = 0 
       ROLLBACK TRANSACTION; 
      ELSE 
       IF XACT_STATE() <> -1 
        ROLLBACK TRANSACTION ProcedureSave; 

      DECLARE @ErrorMessage NVARCHAR(4000); 
      DECLARE @ErrorSeverity INT; 
      DECLARE @ErrorState INT; 
      SELECT @ErrorMessage = ERROR_MESSAGE(); 
      SELECT @ErrorSeverity = ERROR_SEVERITY(); 
      SELECT @ErrorState = ERROR_STATE(); 

      RAISERROR (@ErrorMessage, @ErrorSeverity, @ErrorState); 
     END CATCH 
    GO