Работих при предположението, че един израз в SQL Server е последователен
Това предположение е погрешно. Следните две транзакции имат идентична семантика на заключване:
STATEMENT
BEGIN TRAN; STATEMENT; COMMIT
Никаква разлика. Единичните оператори и автоматичните комити не променят нищо.
Така че обединяването на цялата логика в едно изявление не помага (ако е така, това е било случайно, защото планът се промени).
Нека поправим проблема. SERIALIZABLE
ще коригира несъответствието, което виждате, защото гарантира, че вашите транзакции се държат така, сякаш се изпълняват еднонишково. По същия начин, те се държат така, сякаш са изпълнили незабавно.
Ще получите безизходица. Ако сте наред с цикъла за повторен опит, на този етап сте готови.
Ако искате да инвестирате повече време, приложете заключващи съвети, за да наложите изключителен достъп до съответните данни:
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
)
Сега ще видите намален едновременност. Това може да е напълно добре в зависимост от натоварването ви.
Самото естество на вашия проблем прави постигането на паралелност трудно. Ако имате нужда от решение за това, ще трябва да приложим по-инвазивни техники.
Можете да опростите АКТУАЛИЗИРАНЕТО малко:
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
Това премахва едно ненужно присъединяване.