2009-07-06 3 views
1

Vous trouverez ci-dessous l'extrait de code avec des commentaires qui décrivent l'instruction de problème. Nous avons un déclencheur de mise à jour qui appelle en interne un autre déclencheur de mise à jour sur la même table, en dépit de la propriété Récursif Trigger Enabled définie sur false.Problème de déclencheur de mise à jour récursive dans SQL 2005

Aimeriez-vous comprendre la raison de cette situation car cela provoque un chaos dans mes applications.

/* Drop statements for the table and triggers*/ 

IF EXISTS (SELECT * FROM sys.triggers WHERE object_id = OBJECT_ID(N'[dbo]. [t_upd_TestTrigger_002]')) 
    DROP TRIGGER [dbo].[t_upd_TestTrigger_002] 
IF EXISTS (SELECT * FROM sys.triggers WHERE object_id = OBJECT_ID(N'[dbo].[t_upd_TestTrigger_002]')) 
    DROP TRIGGER [dbo].[t_upd_TestTrigger_001] 
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[TestTrigger]') AND type in (N'U')) 
    DROP TABLE [dbo].[TestTrigger] 


CREATE TABLE [dbo].[TestTrigger] /*Creating a test table*/ 
(
    [InternalKey] INT NOT NULL, 
    [UserModified] varchar(50) DEFAULT SUSER_SNAME() 
) 


/* Please run the snippet below as seperate batch, else you will get 
    an error that 'CREATE TRIGGER' must be the first statement in a 
    query batch. 

    CREATING A UPDATE TRIGGER FOR THE TEST TABLE 
*/ 

CREATE TRIGGER [t_upd_TestTrigger_001] ON [dbo].[TestTrigger] 
FOR UPDATE 
AS 
BEGIN 
    --This trigger has some business logic which gets executed 
    print 'In Trigger 001 ' 
END 

/* Please run the snippet below as separate batch, else you will 
    get an error that 'CREATE TRIGGER' must be the first statement 
    in a query batch. 

    CREATING Another UPDATE TRIGGER FOR THE TEST TABLE 

    This trigger updates the audit fields in the table and it has to be 
    a separate trigger, We cannot combine this with other update triggers - 
    So table TestTrigger will have two FOR UPDATE triggers 
*/ 


CREATE TRIGGER [t_upd_TestTrigger_002] ON [dbo].[TestTrigger] 
FOR UPDATE 
AS 
    print 'bad guy starts' 
UPDATE SRC 
    SET UserModified = SUSER_SNAME() 
    FROM inserted AS INS 
    INNER JOIN dbo.[TestTrigger] AS SRC 
     ON INS.InternalKey = SRC.InternalKey 
     print 'bad guy ends' 

/* INSERTING TEST VALUE IN THE TEST TRIGGER TABLE*/ 

INSERT INTO dbo.[TestTrigger](InternalKey,UserModified) 
SELECT 1 ,'Tester1' UNION ALL 
SELECT 2,'Tester2' UNION ALL 
SELECT 3 ,'Tester3' 

/* TestTrigger table has 3 records, we will update the InternalKey 
    of first record from 1 to 4. We would expect following actions 
    1) [t_upd_TestTrigger_001] to be executed once 
    2) [t_upd_TestTrigger_002] to be executed once 
    3) A message that (1 row(s) affected) only once. 

    On Execution, i find that [t_upd_TestTrigger_002] internally triggers 
    [t_upd_TestTrigger_001]. 

    Please note Database level property Recursive Triggers enabled is 
    set to false. 
*/ 

/*UPDATE THE TABLE SEE THE MESSAGE IN RESULT WINDOW*/ 
UPDATE dbo.[TestTrigger] 
SET InternalKey = 4 
WHERE InternalKey = 1 
+0

Cela prend tout son sens - vous avez un déclencheur de mise à jour effectuant une instruction UPDATE. Bien sûr, cela déclenchera les déclencheurs de mise à jour ... –

Répondre

6

« récursives Déclencheurs activé » ne modifie pas les déclencheurs transitif. Cela signifie que si le déclencheur A met à jour une table d'une manière qui active le déclencheur B et que le déclencheur B met à jour la même table, SQL Server n'a aucun moyen de détecter et d'inhiber cette boucle sans fin. D'autant que le déclencheur B peut mettre à jour d'autres tables, et qu'un déclencheur sur les pourrait mettre à jour la table d'origine à nouveau - cela pourrait devenir aussi complexe que vous le souhaitez.

Finalement, la limite du niveau d'imbrication du déclencheur sera atteinte et la boucle s'arrêtera.

Je suppose que vos deux déclencheurs mettent à jour la table source d'une manière ou d'une autre. SQL Server ne peut détecter les déclencheurs récursifs que si un déclencheur s'active lui-même. Je suppose que ce n'est pas le cas pour vous. Restructurer les déclencheurs est le seul moyen de sortir. Comme une idée (hackery): pouvez ajouter un champ à la table (type de données et la valeur est non pertinente) qui est mis à jour par aucune opération, mais par des déclencheurs. Changez ensuite vos déclencheurs de second ordre pour mettre à jour ce champ. Ajoutez un IF UPDATE() pour ce champ à votre déclencheur de premier ordre. Empêche la mise à jour désormais redondante si le champ a été défini. Si ça a du sens. ;-)

MSDN: Using Nested Triggers, voir les sections «Récursivité directe» et «Récursivité indirecte».

0

Vous pouvez utiliser IF UPDATE(), comme décrit par Tomalak, pour ignorer la logique de déclenchement si UserModified est en cours de mise à jour.

Une autre possibilité consiste à déplacer la colonne UserModified vers une table distincte pour éviter la récursivité.

0

Si vous souhaitez arrêter complètement ce type de comportement dans la base de données, "Pour désactiver la récursivité indirecte, définissez l'option de serveur de déclencheurs imbriqués sur 0 à l'aide de sp_configure." Pour plus d'informations, voir Using Nested Triggers. " Bien sûr, il y a toujours la possibilité que vous utilisiez des triggers imbriqués.

Questions connexes