Działam z założeniem, że pojedyncza instrukcja w SQL Server jest spójna
To założenie jest błędne. Następujące dwie transakcje mają identyczną semantykę blokowania:
STATEMENT
BEGIN TRAN; STATEMENT; COMMIT
Żadna różnica. Pojedyncze oświadczenia i automatyczne zatwierdzenia niczego nie zmieniają.
Więc scalanie całej logiki w jedno stwierdzenie nie pomaga (jeśli tak, to przez przypadek, ponieważ plan się zmienił).
Rozwiążmy problem pod ręką. SERIALIZABLE
naprawi niespójność, którą widzisz, ponieważ gwarantuje, że Twoje transakcje zachowują się tak, jakby były wykonywane jednowątkowo. Podobnie zachowują się tak, jakby zostały wykonane natychmiast.
Będziesz mieć impas. Jeśli możesz wykonać pętlę ponawiania, w tym momencie wszystko jest gotowe.
Jeśli chcesz zainwestować więcej czasu, zastosuj wskazówki dotyczące blokowania, aby wymusić wyłączny dostęp do odpowiednich danych:
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
)
Zobaczysz teraz zmniejszoną współbieżność. To może być całkowicie w porządku w zależności od obciążenia.
Sam charakter Twojego problemu sprawia, że osiągnięcie współbieżności jest trudne. Jeśli potrzebujesz rozwiązania, musimy zastosować bardziej inwazyjne techniki.
Możesz nieco uprościć UPDATE:
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
Pozwala to pozbyć się jednego niepotrzebnego przyłączenia.