重试 INSERT 导致的死锁
Posted
技术标签:
【中文标题】重试 INSERT 导致的死锁【英文标题】:Deadlock caused by retried INSERTs 【发布时间】:2011-12-19 20:46:53 【问题描述】:下面的 sproc 尝试在表中插入一行并生成一个随机 ID,该 ID 用于在相应表上进行 PK。具有随机生成的 ID 的冲突在 catch 块中处理,在该块中再次重试/调用该过程。现在,这需要很长时间并导致死锁,因为锁会保留很长时间。有没有办法在重试前立即解除死锁,以便其他线程可以成功锁定 PK 索引时有一个短暂的窗口?
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
【问题讨论】:
这是用于商业或生产环境的产品吗? 为什么会有多个错误代码用于违反唯一约束?完全不懂SQL server,只是好奇 为什么不使用transactions?在try
之前开始事务并回滚您写comment_ HOW TO RELEASE ... .
而且不需要递归,你可以重写程序并使用循环。请记住,递归会增加堆栈。
为什么不检查生成的 id 是否已经存在于表中之前尝试做和INSERT
?
【参考方案1】:
进程不会阻塞自己的锁。由于对存储过程的调用在同一个进程中运行,因此第二个insert
不可能等待来自第一个insert
的锁。
你能发布一个死锁图吗?这显示了很多关于阻塞进程的信息。
作为一种快速解决方法,您可以在循环中搜索空闲 ID,这将避免大多数(但不是全部)可能的冲突:
while 1=1
begin
EXEC generateRandomPersonId @PersonId=@PersonId OUTPUT
if not exists (select * from Persons where PersonId = @PersonID)
break
end
【讨论】:
+1 - 这绝对是操作应该做的,搜索一个免费的 ID,然后做他的INSERT
它不会自己阻塞,而是与另一个需要获取 PK 索引锁定的过程一起阻塞。至于检查生成的id是否不存在,它不能保证唯一性,因为它不是线程安全的。
添加了一个检查以验证 id 没有被使用,并用循环替换递归调用,瞧!谢谢【参考方案2】:
首先,我希望这个程序只适用于学术环境,而不适用于商业产品或实际生产环境。
这是一种方法:
用循环替换递归 使用事务CREATE PROCEDURE addPerson
(
@FirstName nvarchar(100),
@LastName nvarchar(100)
)
AS
BEGIN
SET NOCOUNT ON;
DECLARE @doed bit
set @doed = 0
DECLARE @PersonId int
WHILE @doed = 0
BEGIN
-- generate random PersonId
-- this sproc can generate ids that already exist in the table
EXEC generateRandomPersonId @PersonId=@PersonId OUTPUT
BEGIN TRANSACTION ExceptionHandling
BEGIN TRY
INSERT INTO [dbo].[Persons]
(
PersonId,FirstName,LastName
)
VALUES
(
@PersonId,@FirstName,@LastName
)
COMMIT TRANSACTION ExceptionHandling
BEGIN CATCH
ROLLBACK TRANSACTION ExceptionHandling
--
-- 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
set @doed = 0
END
ELSE
-- some other error, rethrow it
set @doed = 1
EXEC rethrowError
END
END CATCH
END --end while
END
GO
免责声明:未经测试
【讨论】:
以上是关于重试 INSERT 导致的死锁的主要内容,如果未能解决你的问题,请参考以下文章