Someone wrote the following stored procedure in SQL Server 2008. I think it's just not one the best ways to achieve what he needed to achieve.
As you can see that we need to update MyTable
under some conditions but not under the other. And whenever there is a condition that lets a user update MyTable
we don't want multiple users to update MyTable
concurrently. The thing is the way this procedure is written all the variables are set using select statements and then Begin Transaction
starts.
But given the structure of this procedure is it better to execute an sp_getapplock
procedure to allow just one user to do the update (that might work well with this procedure given its structure but I read all over that sp_getapplock
can lead to deadlocks) and the other options is to do a serializable READ_COMMITED
.
But in case of serializable wouldn't this stored procedure allow one of the concurrent user (out of say 2) still select the old values of col1, col2, col3 and col4 and populate variables like @onGoingSession
, @timeDiff
etc with those old values when the other done updating and released the row (through serializable).Since after the transaction completion it's the turn of second user. And I do wanna block other users from reading the row that is being modified.
Shouldn't we move the begin transaction up right after declaring the variables and the do a serializable after it. So that no concurrent users should read old values and populate the variables while one of the users (apparently who already acquired the row lock) is updating the locked row but hasn't committed as yet.
USE [myDatabase]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[MyTableProc]
(@UserId char(20))
AS
BEGIN
DECLARE @error varchar(200)
DECLARE @warning varchar(300)
DECLARE @timeDiff smallint
DECLARE @onGoingSession bit = 1
DECLARE @userName char(20)
DECLARE @counterOfResync tinyint
SET @counterOfResync = (SELECT [Col1] FROM MyTable)
SET @userName = (SELECT [Col2] FROM MyTable)
SET @onGoingSession = (SELECT [Col3] FROM MyTable)
SET @timeDiff = (SELECT DATEDIFF(MINUTE, Col4, CURRENT_TIMESTAMP) from MyTable)
BEGIN TRANSACTION
IF(@onGoingSession = 1)
BEGIN
IF(@timeDiff >= 360)
BEGIN
UPDATE MyTable
SET UserId = @UserId, Col4 = CURRENT_TIMESTAMP
IF(@@ERROR = 0)
BEGIN
SET @warning = RTRIM('An unfinsihed session for ' +
LTRIM(RTRIM(@userName)) + ' is going on for the past ' +
LTRIM(RTRIM(@timeDiff)) + ' minutes but updates from ' + LTRIM(RTRIM(@UserId)) + ' are successful')
COMMIT
RAISERROR(@warning,7, 1)
RETURN
END
ELSE
BEGIN
ROLLBACK
RETURN @@ERROR
END
END
ELSE
BEGIN
SET @error = RTRIM('A session of updates for '+ LTRIM(RTRIM(@userName))+ ' is already in progress concurrent updates are not allowed')
IF(@@ERROR = 0)
BEGIN
COMMIT
RAISERROR(@error, 8, 1)
RETURN
END
ELSE
BEGIN
ROLLBACK
RETURN @@ERROR
END
END
END
ELSE IF(@onGoingSession = 0 AND @counterOfResync = 0)
BEGIN
UPDATE MyTable
SET UserId = @UserId, Col3 = 1, Col4 = CURRENT_TIMESTAMP
IF(@@ERROR =0)
BEGIN
COMMIT
RETURN
END
ELSE
BEGIN
ROLLBACK
RETURN @@ERROR
END
END
ELSE IF(@onGoingSession = 0 AND @counterOfResync > 0 AND @timeDiff >= 5)
BEGIN
UPDATE MyTable
SET Col3 = 1, CountOfResync = 0, UserId = @UserId, Col4 =
CURRENT_TIMESTAMP
IF(@@ERROR = 0)
BEGIN
COMMIT
RETURN
END
ELSE
BEGIN
ROLLBACK
RETURN @@ERROR
END
END
ELSE
BEGIN
SET @error = RTRIM('A server resync session already in progress, updates can''t be made at the same time')
IF(@@ERROR = 0)
BEGIN
COMMIT
RAISERROR(@error, 9, 1)
RETURN
END
ELSE
BEGIN
ROLLBACK
RETURN @@ERROR
END
END
RETURN @@ERROR
END