Ich bin davon ausgegangen, dass eine einzelne Anweisung in SQL Server konsistent ist
Diese Annahme ist falsch. Die folgenden beiden Transaktionen haben eine identische Sperrsemantik:
STATEMENT
BEGIN TRAN; STATEMENT; COMMIT
Überhaupt kein Unterschied. Einzelne Anweisungen und Auto-Commits ändern nichts.
Es hilft also nicht, die gesamte Logik in einer Aussage zu verschmelzen (falls doch, war es ein Zufall, weil sich der Plan geändert hat).
Lassen Sie uns das vorliegende Problem beheben. SERIALIZABLE
behebt die Inkonsistenz, die Sie sehen, da es garantiert, dass sich Ihre Transaktionen so verhalten, als ob sie Single-Threaded ausgeführt würden. Entsprechend verhalten sie sich so, als ob sie sofort ausgeführt würden.
Sie werden Deadlocks bekommen. Wenn Sie mit einer Wiederholungsschleife einverstanden sind, sind Sie an dieser Stelle fertig.
Wenn Sie mehr Zeit investieren möchten, wenden Sie Sperrhinweise an, um den exklusiven Zugriff auf die relevanten Daten zu erzwingen:
UPDATE Gifts -- U-locked anyway
SET GivenAway = 1
WHERE GiftID = (
SELECT TOP 1 GiftID
FROM Gifts WITH (UPDLOCK, HOLDLOCK) --this normally just S-locks.
WHERE g2.GivenAway = 0
AND (SELECT COUNT(*) FROM Gifts g2 WITH (UPDLOCK, HOLDLOCK) WHERE g2.GivenAway = 1) < 5
ORDER BY g2.GiftValue DESC
)
Sie sehen jetzt eine reduzierte Parallelität. Das kann je nach Auslastung völlig in Ordnung sein.
Die Art Ihres Problems erschwert das Erreichen von Parallelität. Wenn Sie dafür eine Lösung benötigen, müssen wir invasivere Techniken anwenden.
Sie können das UPDATE etwas vereinfachen:
WITH g AS (
SELECT TOP 1 Gifts.*
FROM Gifts
WHERE g2.GivenAway = 0
AND (SELECT COUNT(*) FROM Gifts g2 WITH (UPDLOCK, HOLDLOCK) WHERE g2.GivenAway = 1) < 5
ORDER BY g2.GiftValue DESC
)
UPDATE g -- U-locked anyway
SET GivenAway = 1
Dadurch wird ein unnötiger Join entfernt.