Tôi đã hoạt động với giả định rằng một câu lệnh duy nhất trong SQL Server là nhất quán
Giả định đó là sai. Hai giao dịch sau có ngữ nghĩa khóa giống hệt nhau:
STATEMENT
BEGIN TRAN; STATEMENT; COMMIT
Không có sự khác biệt nào cả. Các câu lệnh đơn và cam kết tự động không thay đổi bất kỳ điều gì.
Vì vậy, việc hợp nhất tất cả logic thành một câu lệnh không giúp ích được gì (nếu có thì đó là do tình cờ vì kế hoạch đã thay đổi).
Hãy khắc phục sự cố trong tầm tay. SERIALIZABLE
sẽ khắc phục sự không nhất quán mà bạn đang thấy vì nó đảm bảo rằng các giao dịch của bạn sẽ hoạt động như thể chúng được thực hiện theo đường đơn. Tương tự, chúng hoạt động như thể chúng thực thi ngay lập tức.
Bạn sẽ gặp bế tắc. Nếu bạn đồng ý với một vòng lặp thử lại, bạn đã hoàn tất tại thời điểm này.
Nếu bạn muốn đầu tư thêm thời gian, hãy áp dụng các gợi ý khóa để buộc quyền truy cập độc quyền vào dữ liệu có liên quan:
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
)
Bây giờ bạn sẽ thấy đồng thời giảm. Điều đó có thể hoàn toàn ổn tùy thuộc vào tải của bạn.
Bản chất vấn đề của bạn khiến việc đạt được đồng thời trở nên khó khăn. Nếu bạn yêu cầu một giải pháp cho điều đó, chúng tôi sẽ cần áp dụng các kỹ thuật xâm lấn hơn.
Bạn có thể đơn giản hóa việc CẬP NHẬT một chút:
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
Thao tác này sẽ loại bỏ một tham gia không cần thiết.