Jag har arbetat under antagandet att en enda sats i SQL Server är konsekvent
Det antagandet är fel. Följande två transaktioner har identisk låssemantik:
STATEMENT
BEGIN TRAN; STATEMENT; COMMIT
Ingen skillnad alls. Enstaka uttalanden och auto-commits ändrar ingenting.
Så att slå samman all logik till ett påstående hjälper inte (om det gör det var det av misstag eftersom planen ändrades).
Låt oss lösa problemet. SERIALIZABLE
kommer att åtgärda inkonsekvensen du ser eftersom det garanterar att dina transaktioner beter sig som om de utfördes entrådigt. På motsvarande sätt beter de sig som om de avrättades omedelbart.
Du kommer att få dödlägen. Om du är ok med en återförsök-loop är du klar vid det här laget.
Om du vill investera mer tid, använd låstips för att tvinga fram exklusiv åtkomst till relevant data:
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
)
Du kommer nu att se minskad samtidighet. Det kan vara helt okej beroende på din belastning.
Själva naturen av ditt problem gör det svårt att uppnå samtidighet. Om du behöver en lösning för det måste vi tillämpa mer invasiva tekniker.
Du kan förenkla UPPDATERINGEN lite:
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
Detta tar bort en onödig anslutning.