web-dev-qa-db-fra.com

sql try/catch rollback/commit - empêche la validation erronée après la restauration

J'essaie d'écrire un script MS SQL qui comporte une transaction et un bloc try/catch. S'il intercepte une exception, la transaction est annulée. Sinon, la transaction est validée. J'ai vu plusieurs sites Web différents disant de le faire comme ceci:

begin transaction
begin try
    --main content of script here
end try
begin catch
    rollback transaction
end catch

commit transaction

Mais ne frapperons-nous toujours pas la ligne "commit transaction" même si une exception est interceptée? Cela ne mènera-t-il pas à une erreur SQL car la transaction a déjà été annulée? Je pense que cela devrait être fait comme ça:

declare @success bit = 1

begin transaction
begin try
    --main content of script here
end try
begin catch
    rollback transaction
    set @success = 0
end catch

if(@success = 1)
begin
    commit transaction
end

Comment la solution couramment publiée n'inclut-elle pas la variable @success? N'y a-t-il pas d'erreur SQL résultant d'une transaction déjà annulée? Ai-je tort de dire que la ligne "commit transaction" du premier exemple de code sera toujours touchée dans le cas de la capture d'une exception?

44
user3666839

J'ai toujours pensé c'était l'un des meilleurs articles sur le sujet. Il inclut l'exemple suivant qui, à mon avis, le clarifie et inclut le @@ trancount, souvent oublié, qui est nécessaire pour des transactions imbriquées fiables.

PRINT 'BEFORE TRY'
BEGIN TRY
    BEGIN TRAN
     PRINT 'First Statement in the TRY block'
     INSERT INTO dbo.Account(AccountId, Name , Balance) VALUES(1, 'Account1',  10000)
     UPDATE dbo.Account SET Balance = Balance + CAST('TEN THOUSAND' AS MONEY) WHERE AccountId = 1
     INSERT INTO dbo.Account(AccountId, Name , Balance) VALUES(2, 'Account2',  20000)
     PRINT 'Last Statement in the TRY block'
    COMMIT TRAN
END TRY
BEGIN CATCH
    PRINT 'In CATCH Block'
    IF(@@TRANCOUNT > 0)
        ROLLBACK TRAN;

    THROW; -- raise error to the client
END CATCH
PRINT 'After END CATCH'
SELECT * FROM dbo.Account WITH(NOLOCK)
GO
65
Gary Walker

Dans votre premier exemple, vous avez raison. Le lot touchera la transaction de validation, que le bloc try soit déclenché ou non.

Dans votre deuxième exemple, je suis d’accord avec les autres commentateurs. L'utilisation de l'indicateur de réussite est inutile. 

Je considère que l'approche suivante est essentiellement une approche allégée des meilleures pratiques.

Si vous voulez voir comment il gère une exception, changez la valeur de la deuxième insertion de 255 à 256.

CREATE TABLE #TEMP ( ID TINYINT NOT NULL );
INSERT  INTO #TEMP( ID ) VALUES  ( 1 )

BEGIN TRY
    BEGIN TRANSACTION

    INSERT  INTO #TEMP( ID ) VALUES  ( 2 )
    INSERT  INTO #TEMP( ID ) VALUES  ( 255 )

    COMMIT TRANSACTION
END TRY
BEGIN CATCH
    DECLARE 
        @ErrorMessage NVARCHAR(4000),
        @ErrorSeverity INT,
        @ErrorState INT;
    SELECT 
        @ErrorMessage = ERROR_MESSAGE(),
        @ErrorSeverity = ERROR_SEVERITY(),
        @ErrorState = ERROR_STATE();
    RAISERROR (
        @ErrorMessage,
        @ErrorSeverity,
        @ErrorState    
        );
    ROLLBACK TRANSACTION
END CATCH

SET NOCOUNT ON

SELECT ID
FROM #TEMP

DROP TABLE #TEMP
27
Jim V.

J'ai utilisé ci-dessous plusieurs fois le modèle de script ms sql avec succès, qui utilise Try-Catch , Commit Transaction- Rollback Transaction , Suivi des erreurs .

Votre bloc TRY sera comme suit

 BEGIN TRY
 BEGIN TRANSACTION T
 ----
 //your script block
 ----
 COMMIT TRANSACTION T 
 END TRY

Votre bloc CATCH sera comme suit

BEGIN CATCH
DECLARE @ErrMsg NVarChar(4000), 
        @ErrNum Int, 
        @ErrSeverity Int, 
        @ErrState Int, 
        @ErrLine Int, 
        @ErrProc NVarChar(200)
 SELECT @ErrNum = Error_Number(), 
       @ErrSeverity = Error_Severity(), 
       @ErrState = Error_State(), 
       @ErrLine = Error_Line(), 
       @ErrProc = IsNull(Error_Procedure(), '-')
 SET @ErrMsg = N'ErrLine: ' + rtrim(@ErrLine) + ', proc: ' + RTRIM(@ErrProc) + ', 
       Message: '+ Error_Message()

Votre script ROLLBACK fera partie du bloc CATCH comme suit

IF (@@TRANCOUNT) > 0 
BEGIN
PRINT 'ROLLBACK: ' + SUBSTRING(@ErrMsg,1,4000)
ROLLBACK TRANSACTION T
END
ELSE
BEGIN
PRINT SUBSTRING(@ErrMsg,1,4000);   
END

END CATCH

Au-dessus de différents blocs de script, vous devez utiliser un seul bloc. Si une erreur survient dans le blocTRY, le blocCATCHest utilisé. Il y définit divers détails sur le numéro d'erreur, la gravité de l'erreur, la ligne d'erreur ..etc. Enfin, tous ces détails seront ajoutés au paramètre @ErrMsg. Ensuite, il va vérifier le nombre de transactions (@@ TRANCOUNT> 0), c'est-à-dire s'il y a quelque chose dans la transaction à restaurer. Si c'est le cas, affichez le message d'erreur et ROLLBACK TRANSACTION . Sinon, imprimez simplement le message d'erreur. 

Nous avons conservé notre script COMMIT TRANSACTION T vers la dernière ligne du bloc TRY afin de nous assurer qu'il doit valider la transaction (modification finale de la base de données) uniquement après que tout le code du bloc TRY a été exécuté avec succès. 

1
Rinoy Ashokan

Compteur de transaction

--@@TRANCOUNT = 0
begin try
--@@TRANCOUNT = 0
BEGIN TRANSACTION tran1
 --@@TRANCOUNT = 1

        --your code
        -- if failed  @@TRANCOUNT = 1
        -- if success @@TRANCOUNT = 0

COMMIT TRANSACTION tran1

end try

begin catch
    print 'FAILED'
end catch
0
Arun Prasad E S

Ci-dessous pourrait être utile.

Source: https://msdn.Microsoft.com/en-us/library/ms175976.aspx

BEGIN TRANSACTION;

BEGIN TRY
    -- your code --
END TRY
BEGIN CATCH
    SELECT 
        ERROR_NUMBER() AS ErrorNumber
        ,ERROR_SEVERITY() AS ErrorSeverity
        ,ERROR_STATE() AS ErrorState
        ,ERROR_PROCEDURE() AS ErrorProcedure
        ,ERROR_LINE() AS ErrorLine
        ,ERROR_MESSAGE() AS ErrorMessage;

    IF @@TRANCOUNT > 0
        ROLLBACK TRANSACTION;
END CATCH;

IF @@TRANCOUNT > 0
    COMMIT TRANSACTION;
GO
0
Sreedhar Chintakunta