Reputation: 4657
I run this script:
BEGIN TRAN Tran1
DECLARE @HighBid AS Money
SET @HighBid = (SELECT CurrentHighBid FROM eBaySchema.tItem WHERE ItemID = 1)
SET @HighBid = @HighBid * 1.1
WAITFOR DELAY '0:00:30'
UPDATE eBaySchema.tItem SET HighBidderID=4, CurrentHighBid = @HighBid WHERE ItemID = 1
COMMIT TRAN Tran1
That will sleep for 30 seconds so it doesn't finish too quickly... Then I run immediately run this, which is the same logic and wants to update the same row in the same table...
BEGIN TRAN Tran1
DECLARE @HighBid AS Money
SET @HighBid = (SELECT CurrentHighBid FROM eBaySchema.tItem WHERE ItemID = 1)
SET @HighBid = @HighBid * 1.1
UPDATE eBaySchema.tItem SET HighBidderID=5, CurrentHighBid = @HighBid WHERE ItemID = 1
COMMIT TRAN Tran1
But the the second script shoots right through and doesn't wait for the first script to let go of the table in the UPDATE statement. What am I missing?
Upvotes: 1
Views: 182
Reputation: 89424
While SERIALIZABLE or REPEATABLE READ will prevent the lost update, it will do it by causing a deadlock, which is pretty inconvenient. Under SERIALIZABLE both sessions will be able to run
SET @HighBid = (SELECT CurrentHighBid FROM eBaySchema.tItem with (UPDLOCK) WHERE ItemID = 1)
But will hold the Shared lock, and then neither will be able to get the Exclusive lock necessary to update the row, which will result in a deadlock, which SQL Server will resolve by failing one sessions.
To block one transaction until the other commits either use sp_getapplock as @RossBush suggests, or use the UPDLOCK table hint.
BEGIN TRAN Tran1
DECLARE @HighBid AS Money
SET @HighBid = (SELECT CurrentHighBid FROM eBaySchema.tItem with (UPDLOCK) WHERE ItemID = 1)
SET @HighBid = @HighBid * 1.1
UPDATE eBaySchema.tItem SET HighBidderID=5, CurrentHighBid = @HighBid WHERE ItemID = 1
COMMIT TRAN Tran1
Upvotes: 2
Reputation: 15185
If you would like to serialize access to an update then you can look into the lock hints posted in a comment above. That is probably the cleanest approach. If you need to define a wait time for your lock then you can create a "single point of truth" procedure that serialized access to a critical section with a configurable timeout. This still uses the internal locking mechanism much like -> SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
CREATE MyCriticalUpdateProcedure()
AS
DECLARE @LockRequestResult INT = 0
DECLARE @MyTimeoutMiliseconds INT=5000--Wait only five seconds max then timeouit
BEGIN TRAN
EXEC @LockRequestResult=SP_GETAPPLOCK 'MyCriticalUpdate','Exclusive','Transaction',@MyTimeoutMiliseconds
IF(@LockRequestResult>=0)BEGIN
--Critical Section
DECLARE @HighBid AS Money
SET @HighBid = (SELECT CurrentHighBid FROM eBaySchema.tItem WHERE ItemID = 1)
SET @HighBid = @HighBid * 1.1
UPDATE eBaySchema.tItem SET HighBidderID=5, CurrentHighBid = @HighBid WHERE ItemID = 1
--Release the lock
COMMIT TRAN
END ELSE
ROLLBACK TRAN
Use the currently configured Sql Server Timeout Settings
CREATE MyCriticalUpdateProcedure()
AS
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
BEGIN TRAN
--Critical Section
DECLARE @HighBid AS Money
SET @HighBid = (SELECT CurrentHighBid FROM eBaySchema.tItem WHERE ItemID = 1)
SET @HighBid = @HighBid * 1.1
UPDATE eBaySchema.tItem SET HighBidderID=5, CurrentHighBid = @HighBid WHERE ItemID = 1
COMMIT TRAN
Upvotes: 0