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

25

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 verwaltet dies die Parallelität NICHT ordnungsgemäß. Nur weil Sie eine Transaktion haben, heißt das nicht, dass jemand anderes nicht den gleichen Wert wie vor der Update-Anweisung liest.

Lassen Sie nun den Code unverändert (ich weiß, dass dies besser als einzelne Anweisung oder noch besser als automatische Inkrementierung / Identitätsspalte gehandhabt wird). Auf welche Weise können Sie sicherstellen, dass der gemeinsame Zugriff ordnungsgemäß verarbeitet wird, und verhindern, dass zwei Clients dieselben Bedingungen erfüllen ID-Wert?

Ich bin mir ziemlich sicher, dass das Hinzufügen von a WITH (UPDLOCK, HOLDLOCK)zum SELECT den Trick macht. Die SERIALIZABLE-Transaktionsisolationsstufe scheint ebenfalls zu funktionieren, da sie es jedem anderen verweigert, zu lesen, was Sie getan haben, bis die Übertragung beendet ist ( UPDATE : Dies ist falsch. Siehe Martins Antwort). Ist das wahr? Funktionieren beide gleich gut? Wird einer dem anderen vorgezogen?

Stellen Sie sich vor, Sie tun etwas Berechtigteres als ein ID-Update - einige Berechnungen basieren auf einem Lesevorgang, den Sie aktualisieren müssen. Es kann viele Tabellen geben, an die Sie schreiben und an die Sie nicht schreiben. Was ist hier die beste Praxis?

Nachdem Sie diese Frage geschrieben haben, sind die Sperrhinweise meines Erachtens besser, da Sie dann nur die Tabellen sperren, die Sie benötigen, aber ich würde mich über jede Eingabe freuen.

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

ErikE
quelle
Nur zur Verdeutlichung: Möchten Sie verhindern, dass 2 Clients denselben Wert lesen oder einen Wert ausgeben update, der möglicherweise auf veralteten Daten basiert? In letzterem rowversionFall können Sie mithilfe der Spalte überprüfen, ob die zu aktualisierende Zeile seit dem Lesen nicht mehr geändert wurde.
a1ex07,
Wir möchten nicht, dass ein zweiter Client den alten ID-Wert erhält, bevor er vom ersten Client auf den neuen Wert aktualisiert wird. Es sollte blockieren.
ErikE

Antworten:

11

Ich spreche nur den SERIALIZABLEAspekt der Isolationsstufe an. Ja, das wird funktionieren, aber mit Deadlock-Risiko.

Zwei Transaktionen können die Zeile gleichzeitig lesen. Sie werden sich nicht gegenseitig blockieren, da sie abhängig von der Tabellenstruktur entweder eine Objektsperre Soder Indexsperren annehmen RangeS-Sund diese Sperren kompatibel sind . Sie blockieren sich jedoch gegenseitig, wenn sie versuchen, die für die Aktualisierung erforderlichen Sperren (Objektsperre IXbzw. Index RangeS-U) abzurufen, die zu einem Deadlock führen.

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

Martin Smith
quelle
+1 aber: für Heap-Tabellen können Sie auch mit Update-Sperren noch einen Conversion-Deadlock erhalten: sqlblog.com/blogs/alexander_kuznetsov/archive/2009/03/11/…
AK
Bizarr, @alex. Ich stelle mir vor, dass es mit einem Rennzustand des Motors zu tun hat, der versucht, herauszufinden, was zu sperren ist, bevor er tatsächlich SPERRT ...
ErikE,
@ErikE - Das Umrechnungsproblem in Alex 'Artikel besteht darin IX, Xauf dem Haufen selbst von nach umzurechnen . Interessanterweise qualifizieren sich keine Zeilen, so dass niemals Zeilensperren aufgehoben werden. Nicht sicher, warum es überhaupt das XSchloss nimmt .
Martin Smith
11

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 alleine und HOLDLOCK ist nicht erforderlich. Manchmal funktioniert sp_getapplock sehr gut. Ich würde hier keine pauschale Aussage treffen - manchmal ändert das Hinzufügen eines weiteren Index, Triggers oder einer indizierten Ansicht das Ergebnis. Wir müssen den Testcode stressen und uns von Fall zu Fall überzeugen.

Ich habe mehrere Beispiele von Stresstests geschrieben hier

Bearbeiten: Für eine bessere Kenntnis der Interna können Sie die Bücher von Kalen Delaney lesen. Es kann jedoch vorkommen, dass Bücher nicht mehr synchron sind, wie bei jeder anderen Dokumentation. Außerdem sind zu viele Kombinationen zu berücksichtigen: sechs Isolationsstufen, viele Arten von Sperren, gruppierte / nicht gruppierte Indizes und wer weiß was noch. Das sind viele Kombinationen. Darüber hinaus ist SQL Server Closed Source, sodass wir keinen Quellcode herunterladen, debuggen und so weiter können - das wäre die ultimative Wissensquelle. Alles andere ist möglicherweise nach der nächsten Version oder dem nächsten Service Pack unvollständig oder veraltet.

Sie sollten sich also nicht entscheiden, was für Ihr System funktioniert, ohne Ihre eigenen Stresstests durchzuführen. Was auch immer Sie gelesen haben, es kann Ihnen helfen zu verstehen, was vor sich geht, aber Sie müssen beweisen, dass der Rat, den Sie gelesen haben, für Sie funktioniert. Ich glaube, niemand kann das für Sie tun.

AK
quelle
9

In diesem speziellen Fall würde die Hinzufügung einer UPDLOCKSperre SELECTtatsächlich Anomalien verhindern. Das Hinzufügen von HOLDLOCKist nicht erforderlich, da eine Aktualisierungssperre für die Dauer der Transaktion aufrechterhalten wird, aber ich gebe zu, dies in der Vergangenheit selbst als (möglicherweise schlechte) Angewohnheit aufgenommen zu haben.

Stellen Sie sich vor, Sie tun etwas Berechtigteres als ein ID-Update. Einige Berechnungen basieren auf einem Lesevorgang, den Sie aktualisieren müssen. Es kann viele Tabellen geben, an die Sie schreiben und an die Sie nicht schreiben. Was ist hier die beste Praxis?

Es gibt keine Best Practice. Die Wahl der Parallelitätskontrolle muss sich nach den Anforderungen der Anwendung richten. 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 gegenseitigen Störungen tolerieren.

  • Abrufen eines gebänderten Lagerbestands (<5, 10+, 50+, 100+) für ein Produkt in einem Webshop = Dirty Read (ungenau ist egal).
  • Überprüfung und Reduzierung des Lagerbestands an dieser Webshop-Kasse = wiederholbares Lesen (wir MÜSSEN den Lagerbestand haben, bevor wir verkaufen, wir MÜSSEN nicht mit einem negativen Lagerbestand enden).
  • Bargeld zwischen meinem Girokonto und dem Sparkonto der Bank verschieben = serialisierbar (mein Bargeld nicht falsch berechnen oder verlegen!).

Edit: @ AlexKuznetsovs Kommentar veranlasste mich, die Frage erneut zu lesen und den sehr offensichtlichen Fehler in meiner Antwort zu beseitigen. Hinweis für Selbstversorger bei verspäteter Veröffentlichung.

Mark Storey-Smith
quelle