Garantiert UPDLOCK die Parallelität?

8

Ich habe eine Tabelle namens tblOrderNumber, die 1 Zeile und 1 Spalte hat. In dieser Tabelle wird die nächste Bestellnummer für meine E-Commerce-Website gespeichert. Es ist ABSOLUT VITAL, dass dieselbe Bestellnummer nicht mehr als einmal verwendet wird. Derzeit verwendet das Team diese gespeicherte Prozedur und es scheint gut zu funktionieren:

Meine Frage ist, garantiert UPDLOCK dies? Ich hätte gedacht, dass auch für SELECT eine Lesesperre erforderlich ist (in dem unwahrscheinlichen Fall, dass 2 Bestellungen innerhalb einer Millisekunde voneinander platziert werden und die erste das UPDATE nicht durchgeführt hat, bevor die zweite, wie ich verstehe, eine SELECT durchgeführt hat In diesem Verfahren gibt es keine Lesesperre.

DECLARE @NextOrderNumber INT

BEGIN TRANSACTION

SELECT @NextOrderNumber = NextOrderNumber
FROM  tblOrderNumber (UPDLOCK)

UPDATE tblOrderNumber
SET   NextOrderNumber = NextOrderNumber + 1

COMMIT

SELECT @NextOrderNumber

--Kundenimplementierung (Wäre UPDLOCK ODER SERIALIZABLE hier besser, da ich nicht denke, dass wir die vollständige Tabelle sperren müssen?)

UPDATE dbo.tblOrderNumber WITH (SERIALIZABLE) 
SET @NextOrderNumber = NextOrderNumber, 
    NextOrderNumber = NextOrderNumber + 1; 
WHERE CustomerId=@CustomerId

Ich verwende SQL Server 2014, werde aber bald zu SQL Azure wechseln.

user1786107
quelle
Dies scheint mir ein wenig unintuitiv zu sein - ich denke, ich würde die Tabelle lieber zuerst aktualisieren, damit (a) ich die Ausgabeklausel sauberer verwenden könnte (b) der Standardstatus der Tabelle darin besteht, die zuletzt zugewiesene Bestellnummer anzuzeigen als der nächste. Das nächste dort zu haben, scheint mir ein wenig zu verlockend, um sich darauf zu verlassen, dass Sie das außerhalb Ihrer Transaktion lesen und davon ausgehen, dass es das ist, was Sie bekommen.
Aaron Bertrand
1
Was ist, wenn die Tabelle mehr als eine Zeile oder keine Zeilen enthält? Ich verstehe, dass das nicht passieren sollte, aber es gibt nichts, was es aufhalten könnte. Wenn ja, wäre SELECT @NextOrderNumber = NextOrderNumber FROM tblOrderNumber (UPDLOCK) nicht deterministisch
Paparazzo

Antworten:

11

Kurze Antwort

Die Update-Sperre ist ausreichend, aber Sie können einfacher erreichen, was Sie wollen:

UPDATE dbo.tblOrderNumber WITH (SERIALIZABLE)
SET @NextOrderNumber = NextOrderNumber,
    NextOrderNumber = NextOrderNumber + 1
WHERE CustomerID = @CustomerID;

Der WITH (SERIALIZABLE)Hinweis ist nicht unbedingt erforderlich, wenn für die Kunden-ID ein eindeutiger Index vorhanden ist.

Längere Antwort

Der Hinweis zur Aktualisierungssperre ist im angegebenen Code ausreichend. Es kann jeweils nur eine Transaktion eine Aktualisierungssperre (U) für eine Ressource erhalten, und Aktualisierungssperren werden bis zum Ende der Transaktion gehalten. Die Aktualisierungssperre wird unmittelbar vor der Änderung in eine exklusive (X) Sperre konvertiert. Die exklusive Sperre bleibt auch bis zum Ende der Transaktion erhalten.

Wenn Sie beim Lesen eine Aktualisierungssperre verwenden , erhalten Sie die von Ihnen gesuchten Parallelitätsgarantien. Um es klar auszudrücken: Sobald die Aktualisierungssperre (durch Auswahl) erworben wurde, kann keine andere Transaktion eine Aktualisierungssperre für dieselbe Ressource erwerben, bis die erste Transaktion festgeschrieben oder abgebrochen wurde.

Sie können die Transaktion (oder einzelne Aktualisierungsanweisung) auch ohne Hinweise auf der SERIALIZABLEIsolationsstufe ausführen . Schließlich ist die Garantie, die Sie suchen, dass die Transaktion nach einem serialisierbaren Zeitplan ausgeführt wird. Wenn Sie sich nicht auf das Wissen über das Sperren von Interna verlassen können, ist es wahrscheinlich einfacher, die gewünschte Isolationsstufe anzugeben und SQL Server die Details zu überlassen.

Natürlich benötigen Sie Code, um auch Fehler oder mögliche Deadlocks zu behandeln. Ich gehe davon aus, dass Sie dies in Ihrem Beispiel weggelassen haben. Ein Belt-and-Braces-Ansatz würde auch SET XACT_ABORT ONfür das Verfahren sicherstellen, dass fast alle möglichen Fehler die Transaktion abbrechen, anstatt stillschweigend fortzufahren.

Es wäre auch möglich, eine robuste Sperrimplementierung manuell mithilfe von Anwendungssperren (mit sp_getapplockund sp_releaseapplock) zu schreiben , aber ehrlich gesagt ist die Verwendung der integrierten serialisierbaren Isolationsstufe wahrscheinlich am einfachsten.

Weitere Informationen finden Sie in meiner Artikelserie:

Paul White 9
quelle