it-swarm.com.de

Verwalten der Parallelität bei Verwendung des SELECT-UPDATE-Musters

Angenommen, Sie haben den folgenden Code (bitte ignorieren Sie, dass er schrecklich ist):

BEGIN TRAN;
DECLARE @id int
SELECT @id = id + 1 FROM TableA;
UPDATE TableA SET id = @id; --TableA must have only one row, apparently!
COMMIT TRAN;
-- @id is returned to the client or used somewhere else

Meines Erachtens wird die Parallelität NICHT ordnungsgemäß verwaltet. Nur weil Sie eine Transaktion haben, bedeutet dies nicht, dass jemand anderes nicht den gleichen Wert liest, den Sie getan haben, bevor Sie zu Ihrer Update-Anweisung gelangen.

Wenn Sie den Code unverändert lassen (mir ist klar, dass dies besser als einzelne Anweisung oder sogar besser mithilfe einer Autoincrement-/Identitätsspalte behandelt wird), was sind sichere Möglichkeiten, um die Parallelität richtig zu handhaben und Race-Bedingungen zu verhindern, die es zwei Clients ermöglichen, dasselbe zu erhalten ID-Wert?

Ich bin mir ziemlich sicher, dass das Hinzufügen einer WITH (UPDLOCK, HOLDLOCK) zu SELECT den Trick macht. Die SERIALIZABLE Transaction Isolation Level scheint ebenfalls zu funktionieren, da sie es jedem anderen verweigert, zu lesen, was Sie getan haben, bis die Übertragung beendet ist ( [~ # ~] update [ ~ # ~] : das ist falsch. Siehe Martins Antwort). Ist das wahr? Werden beide gleich gut funktionieren? Wird einer dem anderen vorgezogen?

Stellen Sie sich vor, Sie machen etwas Legitimeres als ein ID-Update - eine Berechnung basierend auf einem Lesevorgang, den Sie aktualisieren müssen. Es können viele Tabellen beteiligt sein, von denen Sie einige schreiben und andere nicht. Was ist hier die beste Vorgehensweise?

Nachdem ich diese Frage geschrieben habe, denke ich, dass die Sperrhinweise besser sind, weil Sie dann nur die Tabellen sperren, die Sie benötigen, aber ich würde mich über jede Eingabe freuen.

P.S. Und nein, ich kenne die beste Antwort nicht und möchte wirklich ein besseres Verständnis bekommen! :) :)

25
ErikE

Nur den Aspekt der Isolationsstufe SERIALIZABLE ansprechen. Ja, das wird funktionieren, aber mit Deadlock-Risiko.

Zwei Transaktionen können die Zeile gleichzeitig lesen. Sie blockieren sich nicht gegenseitig, da sie entweder eine Objektsperre S oder eine Indexsperre RangeS-S Abhängig von der Tabellenstruktur und diesen Sperren sind kompatibel verwenden. Sie blockieren sich jedoch gegenseitig, wenn sie versuchen, die für die Aktualisierung erforderlichen Sperren (Objekt IX lock bzw. index RangeS-U) Zu erlangen, was zu einem Deadlock führt.

Die Verwendung eines expliziten UPDLOCK -Hinweises serialisiert stattdessen die Lesevorgänge und vermeidet so das Deadlock-Risiko.

11
Martin Smith

Ich denke, der beste Ansatz für Sie wäre, Ihr Modul tatsächlich einer hohen Parallelität auszusetzen und sich selbst davon zu überzeugen. Manchmal reicht UPDLOCK allein aus, und HOLDLOCK ist nicht erforderlich. Manchmal funktioniert sp_getapplock sehr gut. Ich würde hier keine pauschale Aussage machen - manchmal ändert das Hinzufügen eines weiteren Index, Triggers oder einer indizierten Ansicht das Ergebnis. Wir müssen den Testcode belasten und uns von Fall zu Fall selbst davon überzeugen.

Ich habe einige Beispiele für Stresstests geschrieben hier

Bearbeiten: Zur besseren Kenntnis der Interna können Sie die Bücher von Kalen Delaney lesen. Bücher können jedoch wie jede andere Dokumentation nicht mehr synchron sein. Außerdem gibt es zu viele Kombinationen, um sie in Betracht zu ziehen: sechs Isolationsstufen, viele Arten von Sperren, Clustered/Nonclustered-Indizes und wer weiß was noch. Das sind viele Kombinationen. Darüber hinaus ist SQL Server eine geschlossene Quelle, sodass wir keinen Quellcode herunterladen, debuggen und dergleichen können - das wäre die ultimative Wissensquelle. Alles andere kann nach der nächsten Version oder dem nächsten Service Pack unvollständig oder veraltet sein.

Sie sollten sich also nicht ohne eigene Stresstests entscheiden, was für Ihr System funktioniert. Was auch immer Sie gelesen haben, es kann Ihnen helfen, zu verstehen, was los ist, aber Sie müssen beweisen, dass der Rat, den Sie gelesen haben, für Sie funktioniert. Ich glaube nicht, dass jemand das für Sie tun kann.

11
A-K

In diesem speziellen Fall würde das Hinzufügen einer UPDLOCK -Sperre zum SELECT tatsächlich Anomalien verhindern. Das Hinzufügen von HOLDLOCK ist nicht erforderlich, da eine Aktualisierungssperre für die Dauer der Transaktion gehalten wird, aber ich gebe zu, dass ich es selbst als (möglicherweise schlechte) Gewohnheit in der Vergangenheit aufgenommen habe.

Stellen Sie sich vor, Sie tun etwas Legitimeres als ein ID-Update. Eine Berechnung basiert auf einem Lesevorgang, den Sie aktualisieren müssen. Es können viele Tabellen beteiligt sein, von denen Sie einige schreiben und andere nicht. Was ist hier die beste Vorgehensweise?

Es gibt keine Best Practice. Ihre Wahl der Parallelitätskontrolle muss auf den Anforderungen der Anwendung basieren. Einige Anwendungen/Transaktionen müssen so ausgeführt werden, als ob sie das ausschließliche Eigentum an der Datenbank hätten, wodurch Anomalien und Ungenauigkeiten um jeden Preis vermieden werden. Andere Anwendungen/Transaktionen können ein gewisses Maß an gegenseitiger Interferenz tolerieren.

  • Abrufen eines gestapelten Lagerbestands (<5, 10+, 50+, 100+) für ein Produkt in einem Webshop = Dirty Read (ungenau spielt keine Rolle).
  • Überprüfen und Reduzieren des Lagerbestands an dieser Webshop-Kasse = wiederholbares Lesen (wir MÜSSEN den Lagerbestand vor dem Verkauf haben, wir dürfen keinen negativen Lagerbestand haben).
  • Bargeld zwischen meinem Girokonto und dem Sparkonto bei der Bank verschieben = serialisierbar (mein Bargeld nicht falsch berechnen oder verlegen!).

Bearbeiten: @ AlexKuznetsovs Kommentar veranlasste mich, die Frage erneut zu lesen und den sehr offensichtlichen Fehler in meiner Antwort zu beseitigen. Hinweis für sich selbst bei spätem Posting.

9