Der Begriff "Vorschau" kann in den meisten Fällen sehr irreführend sein, abhängig von der Art der Daten, mit denen gearbeitet wird (und die sich von Vorgang zu Vorgang ändern). Was ist zu gewährleisten, dass sich die aktuellen Daten, die gerade bearbeitet werden, zwischen der Erfassung der "Vorschau" -Daten und der Rückkehr des Benutzers nach 15 Minuten in demselben Zustand befinden? Rund um den Block, wieder rein und etwas bei eBay nachsehen - und merkt man, dass sie nicht auf die Schaltfläche "OK" geklickt haben, um den Vorgang tatsächlich durchzuführen und klickt dann endlich auf die Schaltfläche?
Haben Sie eine zeitliche Begrenzung für die Ausführung des Vorgangs, nachdem die Vorschau erstellt wurde? Oder möglicherweise eine Möglichkeit, um festzustellen, ob sich die Daten zum Zeitpunkt der Änderung im selben Status befinden wie zum ersten SELECT
Mal?
Dies ist ein kleiner Punkt, da der Beispielcode möglicherweise hastig erstellt wurde und keinen echten Anwendungsfall darstellt. Warum sollte es jedoch eine "Vorschau" für eine INSERT
Operation geben? Das kann sinnvoll sein, wenn Sie mehrere Zeilen über so etwas wie INSERT...SELECT
einfügen, und es kann eine variable Anzahl von Zeilen eingefügt werden, aber dies ist für eine Singleton-Operation nicht sehr sinnvoll.
Dies ist unerwünscht, weil ... ein relativ geringer Grad an Sicherheit besteht, dass die Vorschaudaten tatsächlich genau wiedergeben, was bei einem Update passieren würde.
Woher kommt genau dieses "geringe Vertrauen"? Es ist zwar möglich, eine andere Anzahl von Zeilen zu aktualisieren als für eine, SELECT
wenn mehrere Tabellen verbunden sind und doppelte Zeilen in einer Ergebnismenge vorhanden sind, dies sollte hier jedoch kein Problem darstellen. Alle Zeilen, die von einem betroffen sein sollen, können einzeln ausgewählt UPDATE
werden. Wenn es eine Nichtübereinstimmung gibt, führen Sie die Abfrage falsch aus.
In Situationen, in denen aufgrund einer JOINed-Tabelle, die mit mehreren Zeilen in der zu aktualisierenden Tabelle übereinstimmt, Duplikate auftreten, wird keine "Vorschau" generiert. Und wenn es eine Gelegenheit gibt, in der dies der Fall ist, muss dem Benutzer erklärt werden, dass er eine Teilmenge des Berichts aktualisiert, die im Bericht wiederholt wird, so dass es nicht als Fehler erscheint, wenn es sich nur um jemanden handelt Betrachten Sie die Anzahl der betroffenen Zeilen.
Der Vollständigkeit halber (obwohl in den anderen Antworten dies erwähnt wurde), verwenden Sie das TRY...CATCH
Konstrukt nicht. Daher können beim Verschachteln dieser Aufrufe leicht Probleme auftreten (auch wenn Sie keine Punkte speichern und keine Transaktionen verwenden). In meiner Antwort auf die folgende Frage hier auf DBA.SE finden Sie eine Vorlage, die Transaktionen über verschachtelte Aufrufe von gespeicherten Prozeduren hinweg verarbeitet:
Müssen wir Transaktionen sowohl im C # -Code als auch in gespeicherten Prozeduren abwickeln?
AUCH WENN die oben genannten Probleme berücksichtigt wurden, liegt immer noch ein kritischer Fehler vor: In der kurzen Zeit, in der der Vorgang ausgeführt wird (dh vor dem ROLLBACK
), können alle Dirty-Read-Abfragen (Abfragen, die WITH (NOLOCK)
oder verwenden SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
) Daten abrufen, die gibt es keinen Moment später. Während jeder, der Dirty-Read-Abfragen verwendet, sich dessen bewusst sein sollte und diese Möglichkeit akzeptiert hat, erhöhen Vorgänge wie diese die Wahrscheinlichkeit, dass Datenanomalien auftreten, die nur schwer zu debuggen sind (was bedeutet, wie viel Zeit Sie damit verbringen möchten, dies zu versuchen) Finden Sie ein Problem, das keine offensichtliche direkte Ursache hat?).
Ein solches Muster verschlechtert auch die Systemleistung, indem es sowohl das Blockieren durch Aufheben weiterer Sperren erhöht als auch mehr Transaktionsprotokollaktivität generiert. (Ich sehe jetzt, dass @MartinSmith diese beiden Punkte auch in einem Kommentar zur Frage erwähnt hat.)
Wenn außerdem Trigger für die zu ändernden Tabellen vorhanden sind, ist möglicherweise eine Menge zusätzlicher Verarbeitungsschritte (CPU- und physische / logische Lesevorgänge) nicht erforderlich. Trigger würden auch die Wahrscheinlichkeit von Datenanomalien, die sich aus unsauberen Lesevorgängen ergeben, weiter erhöhen.
In Bezug auf den oben genannten Punkt - erhöhte Sperren - erhöht die Verwendung der Transaktion die Wahrscheinlichkeit, in Deadlocks zu geraten, insbesondere wenn Trigger involviert sind.
Ein weniger schwerwiegendes Problem, das sich nur auf das weniger wahrscheinliche Szenario von INSERT
Vorgängen beziehen sollte : Die "Vorschau" -Daten stimmen möglicherweise nicht mit denen überein, die in Bezug auf die durch DEFAULT
Einschränkungen ( Sequences
/ NEWID()
/ NEWSEQUENTIALID()
) und festgelegten Spaltenwerte eingefügt wurden IDENTITY
.
Es ist kein zusätzlicher Aufwand erforderlich, um den Inhalt der Tabellenvariablen in die temporäre Tabelle zu schreiben. Dies ROLLBACK
würde sich nicht auf die Daten in der Tabellenvariablen auswirken (weshalb Sie angegeben haben, dass Sie zunächst Tabellenvariablen verwenden). Es wäre also sinnvoller, einfach SELECT FROM @output_to_return;
am Ende zu arbeiten und sich dann nicht einmal die Mühe zu machen, die temporäre Variable zu erstellen Tabelle.
Nur für den Fall, dass diese Nuance der Speicherpunkte nicht bekannt ist (im Beispielcode schwer zu erkennen, da nur eine einzelne gespeicherte Prozedur angezeigt wird): Sie müssen eindeutige Speicherpunktnamen verwenden, damit sich der ROLLBACK {save_point_name}
Vorgang so verhält, wie Sie es erwarten. Wenn Sie die Namen wiederverwenden, wird durch einen ROLLBACK der letzte Speicherpunkt dieses Namens zurückgesetzt, der möglicherweise nicht auf derselben Verschachtelungsebene ROLLBACK
liegt, von der aus der aufgerufen wird. Weitere Informationen zu diesem Verhalten finden Sie im ersten Beispielcodeblock in der folgenden Antwort: Transaktion in einer gespeicherten Prozedur
Das Ausführen einer "Vorschau" ist für Vorgänge mit Blick auf den Benutzer nicht sehr sinnvoll. Ich mache dies häufig für Wartungsvorgänge, damit ich sehen kann, was gelöscht / Garbage Collected wird, wenn ich mit dem Vorgang fortfahre. Ich füge einen optionalen Parameter namens hinzu @TestMode
und mache eine IF
Anweisung, die entweder a tut, SELECT
wenn es @TestMode = 1
sonst das tut DELETE
. Manchmal füge ich den @TestMode
Parameter zu gespeicherten Prozeduren hinzu, die von der Anwendung aufgerufen werden, damit ich (und andere) einfache Tests durchführen können, ohne den Status der Daten zu beeinflussen, aber dieser Parameter wird von der Anwendung nie verwendet.
Nur für den Fall, dass dies nicht aus dem oberen Abschnitt der "Probleme" hervorgeht:
Wenn Sie einen "Vorschau" - / "Test" -Modus benötigen / möchten, um zu sehen, was betroffen sein sollte, wenn die DML-Anweisung ausgeführt werden soll, verwenden Sie NICHT Transaktionen (dh das BEGIN TRAN...ROLLBACK
Muster), um dies zu erreichen. Es ist ein Muster, das bestenfalls auf einem Einzelplatzsystem wirklich funktioniert und in dieser Situation nicht einmal eine gute Idee ist.
Wenn Sie den Großteil der Abfrage zwischen den beiden Zweigen der IF
Anweisung wiederholen, besteht möglicherweise das Problem, dass beide bei jeder Änderung aktualisiert werden müssen. Unterschiede zwischen den beiden Abfragen sind jedoch in der Regel leicht zu erkennen und zu beheben. Andererseits sind Probleme wie Statusunterschiede und Dirty Reads viel schwieriger zu finden und zu beheben. Und das Problem der verringerten Systemleistung ist nicht zu beheben. Wir müssen erkennen und akzeptieren, dass SQL keine objektorientierte Sprache ist, und dass das Einkapseln / Reduzieren von dupliziertem Code kein Entwurfsziel von SQL war, wie es bei vielen anderen Sprachen der Fall war.
Wenn die Abfrage lang / komplex genug ist, können Sie sie in eine Inline-Tabellenwertfunktion einbetten. Dann können Sie SELECT * FROM dbo.MyTVF(params);
für den "Vorschau" -Modus eine einfache und für den "Do it" -Modus eine VERBINDUNG zu den Schlüsselwerten ausführen. Beispielsweise:
UPDATE tab
SET tab.Col2 = tvf.ColB
...
FROM dbo.Table tab
INNER JOIN dbo.MyTVF(params) tvf
ON tvf.ColA = tab.Col1;
Wenn dies ein Berichtsszenario ist, wie Sie es bereits erwähnt haben, wird der erste Bericht in der "Vorschau" ausgeführt. Wenn jemand etwas ändern möchte, das er im Bericht sieht (z. B. einen Status), ist keine zusätzliche Vorschau erforderlich, da erwartet wird, dass die aktuell angezeigten Daten geändert werden.
Wenn die Operation möglicherweise einen Gebotsbetrag um einen bestimmten Prozentsatz oder eine bestimmte Geschäftsregel ändern soll, kann dies in der Präsentationsebene (JavaScript?) Behandelt werden.
Wenn Sie wirklich eine "Vorschau" für eine Endbenutzeroperation durchführen müssen , müssen Sie zuerst den Status der Daten erfassen (möglicherweise ein Hash aller Felder in der Ergebnismenge für UPDATE
Operationen oder die Schlüsselwerte für DELETE
Vergleichen Sie dann vor dem Ausführen der Operation die erfassten Statusinformationen mit den aktuellen Informationen - innerhalb einer Transaktion , die eine HOLD
Sperre für die Tabelle vornimmt, damit sich nach diesem Vergleich nichts ändert - und werfen Sie ein, wenn ein Unterschied vorliegt Fehler und mach ROLLBACK
lieber ein als mit dem UPDATE
oder fortzufahren DELETE
.
UPDATE
Eine Alternative zur Berechnung eines Hashs für die relevanten Felder besteht darin, eine Spalte vom Typ ROWVERSION hinzuzufügen, um Unterschiede für Operationen zu erkennen . Der Wert eines ROWVERSION
Datentyps ändert sich automatisch bei jeder Änderung dieser Zeile. Wenn Sie eine solche Spalte hätten, würden Sie SELECT
sie zusammen mit den anderen "Vorschau" -Daten und dann zusammen mit dem (den) Schlüsselwert (en) und Wert (en) an den Schritt "Sicher, mach weiter und aktualisiere" weitergeben. wechseln. Sie würden dann die ROWVERSION
aus der "Vorschau" übergebenen Werte mit den aktuellen Werten (pro Taste) vergleichen und nur mit dem UPDATE
if ALL fortfahrender Werte stimmen überein. Der Vorteil hierbei ist, dass Sie keinen Hash berechnen müssen, der das Potenzial hat, auch wenn es unwahrscheinlich ist, dass er falsch negativ ist, und jedes Mal etwas Zeit in Anspruch nimmt, wenn Sie den Hash ausführen SELECT
. Andererseits wird der ROWVERSION
Wert nur dann automatisch erhöht, wenn er geändert wird, sodass Sie sich keine Sorgen mehr machen müssen. Der ROWVERSION
Typ ist jedoch 8 Byte, was sich bei vielen Tabellen und / oder Zeilen summieren kann.
Jede dieser beiden Methoden hat Vor- und Nachteile, UPDATE
wenn es darum geht , inkonsistente Zustände im Zusammenhang mit Vorgängen zu erkennen. Sie müssen also ermitteln, welche Methode für Ihr System mehr Vorteile als Nachteile bietet. In beiden Fällen können Sie jedoch vermeiden, dass zwischen dem Erstellen der Vorschau und dem Ausführen des Vorgangs Verzögerungen auftreten, die zu einem Verhalten führen, das außerhalb der Erwartungen des Endbenutzers liegt.
Wenn Sie einen Endbenutzer-facing „Vorschau“ -Modus tun, dann zusätzlich zu dem Zustand der Aufzeichnungen in ausgewählter Zeit erfassen, die entlang und Kontrolle bei Änderung Zeit umfasst eine DATETIME
für SelectTime
und bevölkert über GETDATE()
oder etwas ähnliches. Übergeben Sie dies an die App-Ebene, damit es an die gespeicherte Prozedur zurückgegeben werden kann (meist als einzelner Eingabeparameter), damit es in der gespeicherten Prozedur überprüft werden kann. Dann können Sie feststellen, dass der @SelectTime
Wert nicht länger als X Minuten vor dem aktuellen Wert von liegen darf, wenn die Operation nicht im "Vorschau" -Modus ausgeführt wird GETDATE()
. Vielleicht 2 Minuten? 5 Minuten? Höchstwahrscheinlich nicht mehr als 10 Minuten. Wirft einen Fehler, wenn der DATEDIFF
in MINUTES-Wert über diesem Schwellenwert liegt.