Ich habe einen UPDATE-Trigger für eine Tabelle, die nach einer bestimmten Spalte sucht, die von einem bestimmten Wert zu einem anderen Wert wechselt. In diesem Fall werden einige verwandte Daten in einer anderen Tabelle über eine einzelne UPDATE-Anweisung aktualisiert.
Der Trigger überprüft zunächst, ob bei aktualisierten Zeilen der Wert dieser Spalte gegenüber dem betreffenden Wert geändert wurde. Es verbindet einfach INSERTED mit DELETED und vergleicht den Wert in dieser Spalte. Wenn nichts qualifiziert ist, wird es vorzeitig beendet, sodass die UPDATE-Anweisung nicht ausgeführt wird.
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
In diesem Fall ist CUSTNMBR der Primärschlüssel der zugrunde liegenden Tabelle. Wenn ich eine große Aktualisierung dieser Tabelle durchführe (z. B. mehr als 5000 Zeilen), benötigt diese Anweisung AGES, auch wenn ich die Spalte CUSTCLAS nicht berührt habe. Ich kann beobachten, wie diese Aussage für einige Minuten in Profiler zum Stillstand kommt.
Der Ausführungsplan ist bizarr. Es zeigt einen eingefügten Scan mit 3.714 Ausführungen und ~ 18,5 Millionen Ausgabezeilen. Das läuft durch einen Filter in der CUSTCLAS-Spalte. Es verbindet dies (über eine verschachtelte Schleife) mit einem gelöschten Scan (ebenfalls nach CUSTCLAS gefiltert), der nur einmal ausgeführt wird und 5000 Ausgabezeilen hat.
Was für eine idiotische Sache mache ich hier, um das zu verursachen? Beachten Sie, dass der Trigger mehrzeilige Aktualisierungen unbedingt ordnungsgemäß verarbeiten muss.
EDIT :
Ich habe auch versucht, es so zu schreiben (falls EXISTS etwas Unangenehmes tat), aber es ist immer noch genauso schrecklich.
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
Antworten:
Sie könnten mit expliziten
INNER MERGE JOIN
oderINNER HASH JOIN
Hinweisen auswerten, aber da Sie diese Tabellen vermutlich später im Trigger wieder verwenden, ist es wahrscheinlich besser, nur den Inhaltinserted
und diedeleted
Tabellen in indizierte#temp
Tabellen einzufügen und damit fertig zu sein.Sie erhalten keine nützlichen Indizes, die automatisch für sie erstellt werden.
quelle
CUSTNMBR
, der zum Erstellen des eindeutigen Clustered-Index definiert ist) und denOPTION (RECOMPILE)
Hinweis verwenden, um die Anzahl der Zeilen zu berücksichtigen, oder einfach eine bestimmte Namenskonvention verwenden, z. B.#i_dbo_YourTable
#trigger_name_i
. Wenn ich mit Tabellenvariablen arbeite, muss ich den Code mit expliziten CREATE TABLEs noch mehr überladen. Wir haben kaskadierende Trigger, aber keine rekursiven Trigger, also denke ich, dass ich in Sicherheit bin ...OPTION (RECOMPILE)
damit die Kardinalität berücksichtigt wird.Ich weiß, dass dies beantwortet wurde, aber es ist erst kürzlich als aktiv aufgetaucht, und ich bin auch bei Tabellen mit vielen Millionen Zeilen darauf gestoßen. Obwohl die akzeptierte Antwort nicht abgezinst wird, kann ich zumindest hinzufügen, dass meine Erfahrung zeigt, dass ein Schlüsselfaktor für die Triggerleistung bei ähnlichen Tests (ob eine oder mehrere Spalten tatsächlich ihre Werte geändert haben) darin besteht, ob die Spalte (n) geändert wurden oder nicht. getestet zu werden waren eigentlich Teil der
UPDATE
Aussage. Ich stellte fest, dass der Vergleich von Spalten zwischeninserted
und unddeleted
Tabellen, die tatsächlich nicht Teil derUPDATE
Anweisung waren, die Leistung erheblich beeinträchtigte, die sonst nicht vorhanden wäre, wenn diese Felder Teil von wärenUPDATE
Anweisung (unabhängig davon, ob ihr Wert tatsächlich geändert wird). Warum funktioniert das alles (dh eine Abfrage zum Vergleichen von N Feldern über X Zeilen), um festzustellen, ob sich etwas geändert hat, wenn Sie logisch ausschließen können, dass eine dieser Spalten geändert wird, was offensichtlich nicht möglich ist, wenn sie nicht vorhanden sind in derSET
Klausel derUPDATE
Erklärung.Die Lösung, die ich verwendet habe, war die Verwendung der UPDATE () -Funktion, die nur innerhalb von Triggern funktioniert. Diese integrierte Funktion teilt Ihnen mit, ob in der
UPDATE
Anweisung eine Spalte angegeben wurde , und kann zum Beenden des Triggers verwendet werden, wenn die Spalten, um die Sie sich kümmern, nicht Teil der sindUPDATE
. Dies kann in Verbindung mit a verwendet werden,SELECT
um festzustellen, ob diese Spalten unter der Annahme, dass sie in der vorhandenUPDATE
sind, tatsächliche Änderungen aufweisen. Ich habe Code oben in mehreren Audit-Triggern, der wie folgt aussieht:Diese Logik fährt mit dem Rest des Triggers fort, wenn:
INSERT
SET
Klausel einerUPDATE
und mindestens eine dieser Spalten in einer Zeile hat sich geändertDas
NOT (UPDATE...) OR NOT EXISTS()
mag seltsam oder rückwärts aussehen, aber es soll verhindern, dassSELECT
die Tabellen oninserted
und ausgeführtdeleted
werden, wenn keine der relevanten Spalten Teil der Tabelle istUPDATE
.Abhängig von Ihren Anforderungen ist die Funktion COLUMNS_UPDATED () eine weitere Option, um zu bestimmen, welche Spalten Teil der
UPDATE
Anweisung sind.quelle
UPDATE(CUSTCLAS)
und einfach das Ganze überspringen sollten , wenn falsch (+1). Ich glaube nicht, dass Sie richtig liegen, dass nicht aktualisierte Spalten in den Zeilenversionen nicht so leicht verfügbar sind wie aktualisierte.tempdb
mitDBCC PAGE
tempdb
ich gerade dieses Skript ausprobiert , die Ausgabe in den Editor eingefügt und nach "EEEEEE" gesucht. Ich sehe die Ausgabe im Screenshot hier . Beachten Sie vor und nach Versionen beider Spalten in beiden Zeilen. Es mag viel einfachere Wege geben, aber für meine Zwecke hier ausreichend!tempdb
Seiten gibt, die nicht nebenBBBBBB
oder stehenDDDDDD
. Möglicherweise müssen noch weitere Untersuchungen durchgeführt werden! Vielleicht liegt das aber amREPLICATE
Anruf.Ich könnte versuchen, mit zu schreiben, wenn vorhanden
quelle
http://dave.brittens.org/blog/writing-well-behaved-triggers.html
Laut Dave sollten Sie temporäre Tabellen oder Tabellenvariablen mit Indizes verwenden, da die virtuellen INSERTED / DELETED-Tabellen keine haben. Wenn Sie die Möglichkeit rekursiver Trigger haben, sollten Sie Tabellenvariablen verwenden, um Namenskollisionen zu vermeiden.
Hoffe, jemand findet das hilfreich, da der ursprüngliche Beitrag vor einiger Zeit war ...
quelle
Der folgende Code kann die Leistung dieses Triggers erhöhen. Ich kannte den richtigen Datentyp der Spalte [custclass] nicht , daher müssen Sie ihn anpassen.
Beachten Sie, dass Sie zusätzliche Spalten in diese in Speicherkopien der eingefügten und gelöschten Tabellen aufnehmen können, wenn Sie diese in Ihrem Triggercode benötigen. Die Primärschlüssel in diesen Tabellen erhöhen die Join-Leistung erheblich, wenn mehr als einige Zeilen gleichzeitig aktualisiert werden. Viel Glück!
quelle