J'essaie d'éviter la perte de données lorsque le déclencheur d'insertion sur une table échoue. J'essaye ceci avec le scénario suivant et mon code échoue.
Quand une insertion se produit sur la table Clients, je veux insérer les mêmes données dans la table Archive. Lorsque le déclencheur échoue, je ne veux pas annuler la totalité de la transaction, ce qui entraînerait une perte de données sur la table Clients.
Même si le déclencheur échoue, je souhaite que les données soient insérées dans la table Customers, et la procédure stockée doit retourner Customer_ID comme d'habitude.
ALTER TRIGGER [dbo].[Customer_Insert_Trigger_Test]
ON [dbo].[Customers]
AFTER INSERT
AS
BEGIN
BEGIN TRY
begin transaction;
set nocount on;
SAVE TRANSACTION InsertSaveHere;
--Simulating error situation
RAISERROR (N'This is message %s %d.', -- Message text.
11, -- Severity,
1, -- State,
N'number', -- First argument.
5); -- Second argument.
Insert into Archive select * from Inserted;
commit transaction;
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION InsertSaveHere;
END CATCH
END
Ma question est principalement de savoir comment éviter l'insertion réelle sur la table des clients pour revenir en arrière si le déclencheur échoue. Comment changer mon code pour ça?
La question mentionne que le "code échoue" mais il n'y a aucune indication de message d'erreur ou de ce qui échoue spécifiquement. L'inclusion d'au moins un, sinon les deux, de ces éléments d'information permet toujours d'obtenir de meilleures réponses.
Pour le moment, je vois quelque chose qui semble être une hypothèse incorrecte sur les déclencheurs et les transactions: vous incrémentez le @@TRANCOUNT
en appelant BEGIN TRAN;
mais seulement décrémenter @@TRANCOUNT
lorsqu'il n'y a pas d'erreur et que le COMMIT TRAN;
la ligne s'exécute dans le bloc TRY
. En cas d'erreur, le COMMIT
est ignoré et un ROLLBACK
du point de sauvegarde se produit. Mais l'annulation d'un point de sauvegarde ne diminue pas @@TRANCOUNT
auquel cas l'opération INSERT
se termine et la transaction est toujours active.
Des déclencheurs existent dans une transaction démarrée en interne qui la lie à l'opération DML qui a déclenché le déclencheur. Voici comment vous pouvez appeler ROLLBACK
dans un déclencheur pour annuler cette opération DML.
Dans cet esprit, vous devriez pouvoir supprimer le BEGIN TRAN;
et COMMIT TRAN;
lignes pour que cela fonctionne. L'effet net sera que s'il n'y a pas d'erreur, le INSERT
dans la table Archive
sera validé comme prévu, mais s'il y a une erreur, il fera le ROLLBACK
au point de sauvegarde et continuez.
CEPENDANT, après avoir retiré ces deux pièces, vous vous retrouvez toujours avec la situation délicate d'obtenir l'erreur suivante:
Msg 3931, niveau 16, état 1, procédure Customer_Insert_Trigger_Test, ligne 78
La transaction en cours ne peut pas être validée et ne peut pas être annulée vers un point de sauvegarde. Annulez la transaction entière.
La raison de ce comportement semble être un paramètre implicite de XACT_ABORT ON
par le système lorsqu'il appelle le déclencheur. L'effet de XACT_ABORT ON
consiste à annuler la transaction si une erreur se produit. Le remède? Réglez simplement XACT_ABORT OFF
au début du déclencheur.
Par exemple, ce qui suit fonctionne pour moi:
CREATE
--ALTER
TRIGGER [dbo].[Customer_Insert_Trigger_Test]
ON [dbo].[Inserts]
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT OFF;
BEGIN TRY
PRINT '@@TRANCOUNT = ' + CONVERT(VARCHAR(10), @@TRANCOUNT); -- for debug only
SAVE TRANSACTION InsertSaveHere;
--Simulating error situation
RAISERROR (N'This is message %s %d.', -- Message text.
11, -- Severity,
1, -- State,
N'number', -- First argument.
5); -- Second argument.
--Insert into Archive select * from Inserted;
END TRY
BEGIN CATCH
PRINT 'Entering CATCH block...'; -- for debug only
ROLLBACK TRANSACTION InsertSaveHere;
END CATCH;
END;
Veuillez noter que cette méthode ne pas modifie le comportement attendu des déclencheurs sur ce tableau en ce qui concerne: 1) le COMMIT
se produisant au niveau de la couche la plus externe (soit l'instruction DML initiale, soit au-delà si une transaction explicite a été lancée avant cette instruction), 2) la capacité d'autres déclencheurs potentiels de cette table d'émettre un ROLLBACK pour annuler l'opération , et 3) la capacité d'une transaction explicite, commencée avant l'instruction DML sur cette table, d'émettre un ROLLBACK pour annuler toutes les modifications, y compris l'opération DML sur cette table.
Bien sûr, si la seule chose qui pourrait faire une erreur ici est le INSERT
dans la table Archive
, alors vous pourriez probablement aussi vous débarrasser du SAVE TRAN
et ROLLBACK TRANSACTION InsertSaveHere;
et faites simplement quelque chose dans le bloc CATCH
pour qu'il ne soit pas vide, quelque chose comme DECLARE @Test INT;
pourrait fonctionner. Le raisonnement ici est qu'une seule déclaration DML que les erreurs ne se sont jamais vraiment produites, il n'y a donc rien à annuler ;-).
Pour répondre à la question comme indiqué dans le titre: vous devriez pouvoir COMMIT
dans le déclencheur, mais je serais eXtreeemely attention à faire une telle chose car elle modifie le comportement attendu du moment où la transaction sera validée ou annulée, ce qui pourrait empêcher le bon fonctionnement des autres déclencheurs de cette table (ils ne pourront pas émettre un ROLLBACK
pour annuler l'opération si ce déclencheur s'exécute en premier) et empêcherait le fonctionnement prévu d'une transaction explicite démarrée avant l'opération DML sur cette table.
Pour ce faire (REMARQUE: vous devez avoir lu le paragraphe directement ci-dessus avant de continuer à lire ce paragraphe ), vous devez émettre un COMMIT TRAN;
(puisque le déclencheur existe déjà dans une transaction), puis exécutez un BEGIN TRAN;
. Le COMMIT TRAN;
va valider l'opération DML initiale et BEGIN TRAN;
mettra le @@TRANCOUNT
retour à 1 afin que lorsque l'exécution des déclencheurs se termine, vous n'obtenez pas l'erreur indiquant que le déclencheur s'est terminé avec un autre @@TRANCOUNT
qu'il n'en avait au début.
Un déclencheur fonctionne déjà toujours dans une transaction implicite, voir la question connexe:
Existe-t-il un moyen de garantir qu'un déclencheur SQL Server sera exécuté?
Vous pouvez intercepter une erreur et empêcher un abandon/annulation, mais vous ne pouvez pas "valider" la transaction à partir du déclencheur.