Zagnieżdżone procedury przechowywane zawierające TRY CATCH ROLLBACK pattern?

Jestem zainteresowany skutkami ubocznymi i potencjalnymi problemami następującego wzoru:

CREATE PROCEDURE [Name]
AS
BEGIN
    BEGIN TRANSACTION
    BEGIN TRY
        [...Perform work, call nested procedures...]
    END TRY
    BEGIN CATCH
        ROLLBACK TRANSACTION
        RAISERROR [rethrow caught error using @ErrorNumber, @ErrorMessage, etc]
    END CATCH
END

Według mojego najlepszego zrozumienia, ten wzór jest dobry, gdy jest używany z jedną procedurą - procedura albo zakończy wszystkie swoje oświadczenia bez błędu, albo wycofa wszystkie działania i zgłosi błąd.

Jednakże, gdy jedna procedura składowana wywołuje inną procedurę składowaną, aby wykonać jakąś podgrupę pracy (ze zrozumieniem, że mniejsza procedura jest czasami wywołane samodzielnie) widzę problem związany z wycofaniem-pojawia się komunikat informacyjny (poziom 16) stwierdzający The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION.. Zakładam, że jest tak dlatego, że wycofanie w podprocedurze zawsze wycofuje najbardziej zewnętrzną transakcję, a nie tylko transakcję rozpoczętą w podprocedurze.

Chcę, aby całość została wycofana i przerwana, jeśli wystąpi jakikolwiek błąd (a błąd zgłoszony Klientowi jako błąd SQL), po prostu nie jestem pewien wszystkich efektów ubocznych, które pochodzą z warstwy zewnętrzne próbują wycofać transakcję, która została już wycofana. Być może sprawdzenie @@TRANCOUNT przed wykonaniem rollback przy każdej warstwie try CATCH?

W końcu jest client end (Linq2SQL), który ma własną warstwę transakcyjną:

try
{
    var context = new MyDataContext();
    using (var transaction = new TransactionScope())
    {       
            // Some Linq stuff
        context.SubmitChanges();
        context.MyStoredProcedure();
        transactionComplete();
    }
}
catch
{
    // An error occured!
}

W przypadku, gdy procedura składowana, "MySubProcedure", o nazwie wewnątrz MyStoredProcedure spowoduje błąd, Czy Mogę być pewien, że wszystko, co wcześniej zrobiono w MyStoredProcedure, zostanie wycofane, wszystkie operacje Linq wykonane przez SubmitChanges zostaną wycofane, a na koniec, że błąd zostanie zarejestrowany? Lub co muszę zmienić w moim wzorze, aby upewnić się, że cała operacja jest atomowa, jednocześnie pozwalając na indywidualne użycie części dziecka (tzn. procedury podrzędne powinny nadal mieć tę samą ochronę atomową)

Author: David, 2010-01-15

5 answers

To jest nasz szablon (usunięto błąd logowania)

To jest przeznaczone do obsługi

Objaśnienia:

  • Wszystkie TXN begin I commit/rollbacks muszą być sparowane tak, aby @@TRANCOUNT było takie samo przy wejściu i wyjściu

  • Niedopasowanie @@TRANCOUNT powoduje błąd 266 ponieważ

    • BEGIN TRAN przyrosty @@TRANCOUNT

    • COMMIT dekrety @@TRANCOUNT

    • ROLLBACK zwraca @@TRANCOUNT do zera

  • Nie można zmniejszyć @@TRANCOUNT dla bieżącego zakresu
    To jest to, co myślisz, że jest "wewnętrzna transakcja"

  • SET XACT_ABORT ON tłumi błąd 266 spowodowany niedopasowaniem @@TRANCOUNT
    A także zajmuje się kwestiami takimi jak "SQL Server Transaction Timeout" na dba.se

  • Pozwala to na TXNs po stronie klienta (jak LINQ) Jedna procedura składowana może być częścią transakcji rozproszonej lub XA, lub po prostu zainicjowana w kodzie klienta (np.. NET TransactionScope)

Użycie:

  • każdy przechowywany proc musi być zgodny z tym samym szablonem

Podsumowanie

  • więc nie twórz więcej TXNs niż potrzebujesz

Na kod

CREATE PROCEDURE [Name]
AS
SET XACT_ABORT, NOCOUNT ON

DECLARE @starttrancount int

BEGIN TRY
    SELECT @starttrancount = @@TRANCOUNT

    IF @starttrancount = 0
        BEGIN TRANSACTION

       [...Perform work, call nested procedures...]

    IF @starttrancount = 0 
        COMMIT TRANSACTION
END TRY
BEGIN CATCH
    IF XACT_STATE() <> 0 AND @starttrancount = 0 
        ROLLBACK TRANSACTION;
    THROW;
    --before SQL Server 2012 use 
    --RAISERROR [rethrow caught error using @ErrorNumber, @ErrorMessage, etc]
END CATCH
GO

Uwagi:

  • Kontrola wycofywania jest w rzeczywistości zbędna z powodu SET XACT_ABORT ON. Jednak to sprawia, że czuję się lepiej, wygląda dziwnie Bez i pozwala na sytuacje, w których nie chcesz go na

    {21]}
  • Remus Rusanu mA podobną powłokę , która wykorzystuje punkty zapisu. Wolę atomowe wywołanie DB i nie używam częściowych aktualizacji, takich jak ich Artykuł

 104
Author: gbn,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-25 10:54:21

Nie jestem facetem Linq (i Erland też nie), ale napisał absolutną Biblię na temat obsługi błędów. Poza komplikacjami, które Linq może dodać do twojego problemu, na wszystkie inne pytania należy odpowiedzieć tutaj:

Http://www.sommarskog.se/error_handling/Part1.html

(Stary link: http://www.sommarskog.se/error_handling_2005.html )

 10
Author: Aaron Bertrand,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-18 18:06:58

Aby rozwiązać problem zwracania numeru błędu i numeru linii, o którym wspomniał @AlexKuznetsov, można podnieść błąd jako taki:

DECLARE @ErrorMessage NVARCHAR(4000)
DECLARE @ErrorSeverity INT
DECLARE @ErrorState INT
DECLARE @ErrorLine INT
DECLARE @ErrorNumber INT

SELECT @ErrorMessage = ERROR_MESSAGE(),
@ErrorSeverity = ERROR_SEVERITY(),
@ErrorState = ERROR_STATE(),
@ErrorNumber = ERROR_NUMBER(),
@ErrorLine = ERROR_LINE()

RAISERROR (@ErrorMessage, @ErrorSeverity, @ErrorState, @ErrorNumber, @ErrorLine)
 1
Author: Amanda,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-04-29 05:57:44

-- @ Amanda metoda powyżej nie zwraca poprawnego numeru błędu

DECLARE  
  @ErrorMessage   nvarchar(4000),  
  @ErrorSeverity   int,  
  @ErrorState int,  
  @ErrorLine  int,  
  @ErrorNumber   int  

BEGIN TRY  
 SELECT 1/0; -- CATCH me  
END TRY  

BEGIN CATCH  

  DECLARE @err int = @@ERROR  

  PRINT @err           -- 8134, divide by zero  
  PRINT ERROR_NUMBER() -- 8134  

  SELECT  
    @ErrorMessage  = ERROR_MESSAGE(),  
    @ErrorSeverity = ERROR_SEVERITY(),  
    @ErrorState    = ERROR_STATE(),  
    @ErrorNumber   = ERROR_NUMBER(),  
    @ErrorLine     = ERROR_LINE()  

  -- error number = 50000 :(  
  RAISERROR (@ErrorMessage, @ErrorSeverity, @ErrorState, @ErrorNumber, @ErrorLine)  

END CATCH  

-- error number = 8134  
SELECT 1/0
 0
Author: Ben Tennen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-03 19:08:22

W przypadku, gdy nie jest wymagana specjalna obsługa błędów w CATCH, z wyjątkiem rethrow i przechowywanego łańcucha wywołań procs, może być odpowiednie użycie takiego prostego szablonu:

create procedure someNestedSP
as
SET XACT_ABORT ON
begin transaction
-- do some work or call some other similar SP
commit transaction

W przypadku błędu cofnęłoby to również transakcję roota ze wszystkimi "zagnieżdżonymi", ale kod jest krótszy i prostszy niż rozwiązanie @gbn. Nadal XACT_ABORT zajmuje się większością poruszanych tam kwestii.

Mogą być dodatkowe koszty na zagnieżdżanie transakcji, ale może nie być zbyt wysokie, I Zgadnij.

 -1
Author: Vadzim,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-23 09:46:41