SQL Server - les transactions reviennent en cas d'erreur?

193

Nous avons une application client qui exécute du SQL sur un SQL Server 2005, comme suit:

BEGIN TRAN;
INSERT INTO myTable (myColumns ...) VALUES (myValues ...);
INSERT INTO myTable (myColumns ...) VALUES (myValues ...);
INSERT INTO myTable (myColumns ...) VALUES (myValues ...);
COMMIT TRAN;

Il est envoyé par une commande de chaîne longue.

Si l'une des insertions échoue ou si une partie de la commande échoue, SQL Server annule-t-il la transaction? S'il n'effectue pas de restauration, dois-je envoyer une deuxième commande pour l'annuler?

Je peux donner des détails sur l'API et la langue que j'utilise, mais je pense que SQL Server devrait répondre de la même manière pour n'importe quelle langue.

jonathanpeppers
la source

Réponses:

205

Vous pouvez mettre set xact_abort onavant votre transaction pour vous assurer que sql revient automatiquement en cas d'erreur.

Greg B
la source
1
Cela fonctionnera-t-il sur MS SQL 2K et supérieur? Cela semble la solution la plus simple.
jonathanpeppers
1
Il apparaît dans la documentation pour 2000, 2005 et 2008, donc je suppose que oui. Nous l'utilisons en 2008.
8
Dois-je le désactiver ou est-ce par session?
Marc
5
@Marc la portée de xact_abortest au niveau de la connexion.
Keith
2
@AlexMcMillan L'instruction DROP PROCEDURE modifie la structure de la base de données, contrairement à INSERT, qui ne fonctionne qu'avec les données. Il ne peut donc pas être inclus dans une transaction. Je simplifie à l'extrême, mais c'est fondamentalement comme ça.
eksortso
195

Vous avez raison de dire que toute la transaction sera annulée. Vous devez émettre la commande pour l'annuler.

Vous pouvez envelopper ceci dans un TRY CATCHbloc comme suit

BEGIN TRY
    BEGIN TRANSACTION

        INSERT INTO myTable (myColumns ...) VALUES (myValues ...);
        INSERT INTO myTable (myColumns ...) VALUES (myValues ...);
        INSERT INTO myTable (myColumns ...) VALUES (myValues ...);

    COMMIT TRAN -- Transaction Success!
END TRY
BEGIN CATCH
    IF @@TRANCOUNT > 0
        ROLLBACK TRAN --RollBack in case of Error

    -- you can Raise ERROR with RAISEERROR() Statement including the details of the exception
    RAISERROR(ERROR_MESSAGE(), ERROR_SEVERITY(), 1)
END CATCH
Raj Plus
la source
2
J'aime mieux la solution de DyingCactus, c'est 1 ligne de code à changer. Si le vôtre est meilleur (ou plus fiable) pour une raison quelconque, faites-le moi savoir.
jonathanpeppers
14
Le try catch vous donne la possibilité de capturer (et éventuellement de corriger) l'erreur et de générer un message d'erreur personnalisé si nécessaire.
Raj More
11
«Capturer et enregistrer» plus fréquemment que «capturer et réparer», je pense.
quillbreaker
24
La syntaxe de RAISERROR est incorrecte au moins dans SQL Server 2008R2 et versions ultérieures. Voir msdn.microsoft.com/en-us/library/ms178592.aspx pour la syntaxe correcte.
Eric J.
2
@BornToCode Pour vous assurer que la transaction existe .. Disons que vous avez annulé votre transaction sous une condition donnée (dans le try), mais le code échoue après. Il n'y a plus de transaction, mais vous entrez toujours dans le catch.
Gabriel GM
42

Voici le code pour obtenir le message d'erreur fonctionnant avec MSSQL Server 2016:

BEGIN TRY
    BEGIN TRANSACTION 
        -- Do your stuff that might fail here
    COMMIT
END TRY
BEGIN CATCH
    IF @@TRANCOUNT > 0
        ROLLBACK TRAN

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

    -- Use RAISERROR inside the CATCH block to return error  
    -- information about the original error that caused  
    -- execution to jump to the CATCH block.  
    RAISERROR (@ErrorMessage, @ErrorSeverity, @ErrorState);
END CATCH
samwise
la source
1
J'ai dû utiliser DECLARE @Var TYPE; SET @Var = ERROR;pour augmenter les erreurs dans le serveur SQL 2005. Sinon, le code ci-dessus pour augmenter les erreurs fonctionne aussi pour les anciennes bases de données. Essayer d'attribuer une valeur par défaut à une variable locale est ce qui causait le problème.
jtlindsey
Vous pouvez utiliser un simple JETER; au lieu des déclarations RAISERROR et ERROR_ *.
rodzmkii
21

De l'article MDSN, Controlling Transactions (Database Engine) .

Si une erreur d'instruction d'exécution (telle qu'une violation de contrainte) se produit dans un lot, le comportement par défaut dans le moteur de base de données consiste à restaurer uniquement l'instruction qui a généré l'erreur. Vous pouvez modifier ce comportement à l'aide de l'instruction SET XACT_ABORT. Une fois SET XACT_ABORT ON exécuté, toute erreur d'instruction d'exécution provoque une annulation automatique de la transaction en cours. Les erreurs de compilation, telles que les erreurs de syntaxe, ne sont pas affectées par SET XACT_ABORT. Pour plus d'informations, consultez SET XACT_ABORT (Transact-SQL).

Dans votre cas, il annulera la transaction complète lorsque l'un des insertions échouera.

Vitaly
la source
3
de quoi avons-nous besoin pour gérer les erreurs de syntaxe? ou compiler des erreurs? si l'un d'entre eux se produit, toute la transaction doit être
annulée
Les erreurs de compilation / syntaxe sont à l'origine des projets SSDT. :-)
Joe the Coder
10

Si l'une des insertions échoue ou si une partie de la commande échoue, le serveur SQL annule-t-il la transaction?

Non.

S'il n'effectue pas de restauration, dois-je envoyer une deuxième commande pour l'annuler?

Bien sûr, vous devriez émettre ROLLBACKau lieu deCOMMIT .

Si vous souhaitez décider de valider ou d'annuler la transaction, vous devez supprimer la COMMITphrase de l'instruction, vérifier les résultats des insertions, puis émettre l'une COMMITou l' autre ou en ROLLBACKfonction des résultats de la vérification.

Quassnoi
la source
Donc, si j'obtiens une erreur, dites "Conflit de clé primaire" Je dois envoyer un deuxième appel pour revenir en arrière? Je suppose que cela a du sens. Que se passe-t-il s'il y a une erreur liée au réseau telle que la connexion est interrompue pendant une instruction SQL très longue?
jonathanpeppers
2
Lorsqu'une connexion expire, le protocole réseau sous-jacent (par exemple Named Pipesou TCP) interrompt la connexion. Lorsqu'une connexion est interrompue, SQL Serverarrête toutes les commandes en cours d'exécution et annule la transaction.
Quassnoi
1
La solution de DyingCactus semble donc résoudre mon problème, merci pour l'aide.
jonathanpeppers
Si vous devez abandonner sur une erreur, alors oui, c'est la meilleure option.
Quassnoi