(Hinweis: Dies ist für MS SQL Server)
Angenommen, Sie haben ein Tabellen-ABC mit einer Primärschlüssel-Identitätsspalte und einer CODE-Spalte. Wir möchten, dass jede Zeile einen eindeutigen, sequenziell generierten Code enthält (basierend auf einer typischen Prüfziffernformel).
Nehmen wir an, Sie haben eine andere Tabelle DEF mit nur einer Zeile, die den nächsten verfügbaren CODE speichert (stellen Sie sich eine einfache automatische Nummer vor).
Ich weiß, dass Logik wie unten eine Race-Bedingung darstellen würde, in der zwei Benutzer mit dem gleichen CODE enden könnten:
%Vor%Ich weiß, dass zwei Benutzer in Schritt 1) stecken bleiben könnten und am Ende mit dem gleichen CODE in der ABC-Tabelle enden könnten.
Was ist der beste Weg, um mit dieser Situation umzugehen? Ich dachte, ich könnte einfach ein "begin tran" / "commit tran" um diese Logik wickeln, aber ich denke nicht, dass es funktioniert hat. Ich hatte eine gespeicherte Prozedur wie diese zu testen, aber ich habe die Race-Bedingung nicht vermieden, als ich aus zwei verschiedenen Fenstern in MS lief:
%Vor%Kann jemand etwas Licht darauf werfen? Ich dachte, die Transaktion würde verhindern, dass ein anderer Benutzer auf meine NextCodeTable zugreifen kann, bis die erste Transaktion abgeschlossen ist, aber ich denke, mein Verständnis von Transaktionen ist fehlerhaft.
EDIT: Ich habe versucht, die Wartezeit nach der "update" -Anweisung zu verschieben, und ich habe zwei verschiedene Codes ... aber ich vermutete das. Ich habe dort die waitfor-Anweisung, um eine Verzögerung zu simulieren, so dass der Race-Zustand leicht zu sehen ist. Ich denke, das Hauptproblem ist meine falsche Wahrnehmung der Funktionsweise von Transaktionen.
Legen Sie die Transaktionsisolationsstufe auf Serializable fest.
Bei niedrigeren Isolationsstufen können andere Transaktionen die Daten in einer Zeile lesen, die in dieser Transaktion gelesen, aber noch nicht geändert wurde. Zwei Transaktionen können also den gleichen Wert lesen. Bei sehr geringer Isolation (Read Uncommitted) können andere Transaktionen Daten sogar lesen, nachdem sie geändert wurden (aber bevor sie festgeschrieben wurden) ...
Überprüfen Sie Details zu SQL Server-Isolationsstufen hier
Unter dem Strich ist also die Isolationsebene ein kritisches Stück, um zu steuern, auf welcher Ebene des Zugriffs andere Transaktionen in diese gelangen.
HINWEIS. Im Link finden Sie serialisierbare
Anweisungen kann keine Daten lesen, die von anderen Transaktionen geändert, aber noch nicht festgelegt wurden. .
Dies liegt daran, dass die Sperren beim Ändern der Zeile platziert werden, nicht wenn Begin Trans
auftritt. Daher können Sie mit einer anderen Transaktion möglicherweise noch den alten Wert bis zu dem Punkt lesen, an dem Sie ihn geändert haben. Also würde ich die Logik ändern, um sie in derselben Anweisung zu ändern, wie Sie sie gelesen haben, und gleichzeitig die Sperre setzen.
Wie andere Responder erwähnt haben, können Sie die Transaktionsisolationsstufe festlegen, um sicherzustellen, dass alles, was Sie mit einer SELECT-Anweisung "gelesen" haben, innerhalb einer Transaktion nicht geändert werden kann.
Alternativ können Sie eine Sperre speziell für die DEF-Tabelle entfernen, indem Sie nach dem Tabellennamen die Syntax WITH HOLDLOCK
hinzufügen, z. B.
Es macht hier keinen großen Unterschied, da Ihre Transaktion klein ist, aber es kann nützlich sein, Sperren für einige SELECTs und nicht für andere innerhalb einer Transaktion zu entfernen. Es ist eine Frage der "Wiederholbarkeit gegen Nebenläufigkeit".
Ein paar relevante MS-SQL-Dokumente.
Wiederholung:
Also, raten Sie, dass Sie das gleichzeitig in zwei Fenstern (A und B) ausgeführt haben:
Versuchen Sie, die wait-Anweisung nach dem Update vor dem Commit zu setzen und zu sehen, was passiert.
Dies ist tatsächlich ein häufiges Problem in SQL-Datenbanken und das ist der Grund, warum die meisten (alle?) von ihnen einige eingebaute Funktionen haben, um sich um dieses Problem zu kümmern, eine eindeutige Kennung zu erhalten. Hier sind einige Dinge zu sehen, wenn Sie Mysql oder Postgres verwenden. Wenn Sie eine andere Datenbank verwenden, wette ich, dass etwas sehr ähnlich ist.
Ein gutes Beispiel dafür sind Postgres-Sequenzen, die Sie hier sehen können:
Mysql verwendet etwas, das automatische Inkremente genannt wird.
Sie können die Spalte auf einen berechneten Wert setzen, der beibehalten wird. Dies wird für die Race Condition sorgen.
HINWEIS
Wenn Sie diese Methode verwenden, müssen Sie den nächsten Code nicht in einer Tabelle speichern. Die Code-Spalte wird zum Bezugspunkt.
Implementierung
Geben Sie der Spalte die folgenden Eigenschaften unter der berechneten Spaltenspezifikation an.
Formel = dbo.GetNextCode ()
Wird beibehalten = Ja
%Vor%Tags und Links sql sql-server transactions