The following sproc attempts to insert a row into a table and generate a random ID, which is used for a PK on the corresponding table. Collisions with randomly generated IDs are handled in the catch block, where the procedure is retried/called again. Now, this takes a long time and causes deadlocks, because locks are kept for an extended period of time. Is there a way to release deadlocks immediately before retrying, so that there is a short window when other threads can succeed in locking the PK index?
CREATE PROCEDURE addPerson
(
@FirstName nvarchar(100),
@LastName nvarchar(100)
)
AS
BEGIN
SET NOCOUNT ON;
DECLARE @PersonId int
-- generate random PersonId
-- this sproc can generate ids that already exist in the table
EXEC generateRandomPersonId @PersonId=@PersonId OUTPUT
BEGIN TRY
INSERT INTO [dbo].[Persons]
(
PersonId,FirstName,LastName
)
VALUES
(
@PersonId,@FirstName,@LastName
)
BEGIN CATCH
--
-- HOW TO RELEASE LOCKS HERE that are still held
-- for the previous INSERT statement?
--
DECLARE @ErrorNumber int, @ErrorMessage nvarchar(2048)
SELECT @ErrorNumber=ERROR_NUMBER(),
@ErrorMessage=ERROR_MESSAGE()
-- if a race condition happened and
-- PersonId happened to be picked already, retry all over again
IF (@ErrorNumber = 2601 OR @ErrorNumber = 2627 AND CHARINDEX(N'PK_Persons_PersonId', @ErrorMessage) > 0)
BEGIN
--
-- RETRYING HERE participates in a high possibility and
-- occurrence of deadlocks
--
EXEC addPerson @FirstName,@LastName
END
ELSE
-- some other error, rethrow it
EXEC rethrowError
END
END CATCH
END
GO
A process does not block on its own locks. Since a call to a stored procedure runs in the same process, it's impossible for the second insert
to wait for a lock from the first insert
.
Could you post a deadlock graph? That shows a lot of information on the blocking processes.
As a quick fix, you could search for a free ID in a loop, which would avoid most (but not all) possible clashes:
while 1=1
begin
EXEC generateRandomPersonId @PersonId=@PersonId OUTPUT
if not exists (select * from Persons where PersonId = @PersonID)
break
end