J'ai un déclencheur UPDATE sur une table qui surveille une colonne spécifique passant d'une valeur spécifique à une autre valeur. Lorsque cela se produit, il met à jour certaines données associées dans une autre table via une seule instruction UPDATE.
La première chose que le déclencheur fait est de vérifier si des lignes mises à jour ont changé la valeur de cette colonne par rapport à la valeur en question. Il joint simplement INSERTED à DELETED et compare la valeur de cette colonne. Si rien ne se qualifie, il sort tôt pour que l'instruction UPDATE ne s'exécute pas.
IF NOT EXISTS (
SELECT TOP 1 i.CUSTNMBR
FROM INSERTED i
INNER JOIN DELETED d
ON i.CUSTNMBR = d.CUSTNMBR
WHERE d.CUSTCLAS = 'Misc'
AND i.CUSTCLAS != 'Misc'
)
RETURN
Dans ce cas, CUSTNMBR est la clé primaire de la table sous-jacente. Si je fais une grande mise à jour sur cette table (disons, plus de 5000 lignes), cette instruction prend AGES, même si je n'ai pas touché la colonne CUSTCLAS. Je peux le regarder caler sur cette déclaration pendant plusieurs minutes dans Profiler.
Le plan d'exécution est bizarre. Il montre un scan inséré avec 3 714 exécutions et ~ 18,5 millions de lignes de sortie. Cela passe par un filtre sur la colonne CUSTCLAS. Il joint cela (via une boucle imbriquée) à un balayage supprimé (également filtré sur CUSTCLAS), qui ne s'exécute qu'une seule fois et possède 5000 lignes de sortie.
Quelle chose idiote fais-je ici pour provoquer cela? Notez que le déclencheur doit absolument gérer correctement les mises à jour multi-lignes.
MODIFIER :
J'ai aussi essayé de l'écrire comme ça (au cas où EXISTS faisait quelque chose de désagréable), mais c'est toujours aussi terrible.
DECLARE @CUSTNMBR varchar(31)
SELECT TOP 1 @CUSTNMBR = i.CUSTNMBR
FROM INSERTED i
INNER JOIN DELETED d
ON i.CUSTNMBR = d.CUSTNMBR
WHERE d.CUSTCLAS = 'Misc'
AND i.CUSTCLAS != 'Misc'
IF @CUSTNMBR IS NULL
RETURN
Réponses:
Vous pouvez évaluer en utilisant des explicites
INNER MERGE JOIN
ou desINNER HASH JOIN
conseils, mais étant donné que vous utilisez probablement ces tables à nouveau plus tard dans le déclencheur, vous feriez probablement mieux d'insérer simplement le contenuinserted
et lesdeleted
tables dans des#temp
tables indexées et d'en finir avec.Ils n'obtiennent pas automatiquement des index utiles créés pour eux.
la source
CUSTNMBR
pour créer l'index clusterisé unique) et utiliser l'OPTION (RECOMPILE)
indice pour qu'il prenne en compte le nombre de lignes ou peut-être simplement utiliser une convention de dénomination particulière telle que#i_dbo_YourTable
#trigger_name_i
. Si je choisis des variables de table, je devrai encore plus encombrer le code avec des CREATE TABLE explicites. Nous avons des déclencheurs en cascade, mais pas des déclencheurs récursifs, donc je pense que je serai en sécurité ...OPTION (RECOMPILE)
, la cardinalité est donc prise en compte.Je sais que cela a été répondu, mais il est apparu comme récemment actif et je l'ai rencontré aussi pour des tables avec plusieurs millions de lignes. Bien que je n'écarte pas la réponse acceptée, je peux au moins ajouter que mon expérience montre qu'un facteur clé des performances de déclenchement lors de tests similaires (voir si une ou plusieurs colonnes ont réellement changé leurs valeurs) est de savoir si la ou les colonnes en cours de test faisaient en fait partie de la
UPDATE
déclaration. J'ai trouvé que la comparaison des colonnes entre les tablesinserted
etdeleted
qui ne faisaient en fait pas partie de l'UPDATE
instruction affectait considérablement les performances qui n'existaient pas autrement si ces champs faisaient partie de laUPDATE
(quelle que soit leur valeur réellement modifiée). Pourquoi tout cela fonctionne (c'est-à-dire une requête pour comparer N champs sur X lignes) pour déterminer si quelque chose a changé si vous pouvez exclure logiquement la possibilité de modifier l'une de ces colonnes, ce qui n'est évidemment pas possible si elles n'étaient pas présentes dans laSET
clause de laUPDATE
déclaration.La solution que j'ai employée était d'utiliser la fonction UPDATE () qui ne fonctionne qu'à l'intérieur des déclencheurs. Cette fonction intégrée vous indique si une colonne a été spécifiée dans l'
UPDATE
instruction et peut être utilisée pour quitter le déclencheur si les colonnes qui vous intéressent ne font pas partie de laUPDATE
. Cela peut être utilisé conjointement avec aSELECT
pour déterminer si ces colonnes, en supposant qu'elles sont présentes dans leUPDATE
, ont des changements réels. J'ai du code en haut de plusieurs déclencheurs d'audit qui ressemble à:Cette logique passera au reste du déclencheur si:
INSERT
SET
clause d'unUPDATE
et au moins une de ces colonnes dans une ligne a changéLe
NOT (UPDATE...) OR NOT EXISTS()
peut sembler étrange ou en arrière, mais il est conçu pour éviter de faire leSELECT
sur les tablesinserted
etdeleted
si aucune des colonnes pertinentes ne fait partie de laUPDATE
.Selon vos besoins, la fonction COLUMNS_UPDATED () est une autre option pour déterminer quelles colonnes font partie de l'
UPDATE
instruction.la source
UPDATE(CUSTCLAS)
et simplement sauter le tout si faux (+1). Je ne pense pas que vous ayez raison de dire que les colonnes non mises à jour ne sont pas aussi facilement disponibles dans les versions de lignes que celles mises à jour.tempdb
avecDBCC PAGE
tempdb
j'ai juste essayé ce script , collé la sortie dans le bloc-notes et recherché "EEEEEE". Je vois la sortie dans la capture d'écran ici . Notez les versions avant et après des deux colonnes dans les deux lignes. Il peut y avoir des moyens beaucoup plus faciles mais suffisants pour mes besoins ici!tempdb
pages non à côté deBBBBBB
ouDDDDDD
. Il faudra peut-être enquêter un peu plus! Mais c'est peut-être dû à l'REPLICATE
appel.Je pourrais essayer de réécrire en utilisant s'il existe
la source
http://dave.brittens.org/blog/writing-well-behaved-triggers.html
Selon Dave, vous devez utiliser des tables temporaires ou des variables de table avec des index, car les tables virtuelles INSERTED / DELETED n'en ont pas. Si vous avez la possibilité de déclencheurs récursifs, vous devez utiliser des variables de table pour éviter les collisions de noms.
J'espère que quelqu'un trouvera cela utile car le message d'origine était il y a un certain temps ...
la source
Le code suivant peut augmenter les performances de ce déclencheur. Je ne connaissais pas le type de données correct de la colonne [custclass] , vous devez donc l'ajuster.
Notez que vous pouvez inclure des colonnes supplémentaires dans ces copies en mémoire des tables insérées et supprimées si vous en avez besoin dans votre code de déclenchement. Les clés primaires de ces tables augmenteront considérablement les performances de jointure lors de la mise à jour de plusieurs lignes à la fois. Bonne chance!
la source