由事务隔离级别分隔的并发进程死锁

Posted

技术标签:

【中文标题】由事务隔离级别分隔的并发进程死锁【英文标题】:deadlock on concurrent processes separated by transaction isolation level 【发布时间】:2011-08-23 13:36:05 【问题描述】:

我们有一个工单表。服务器代理作业从游标中的该表中获取 100 个条目并执行一些工作。为了并行化,有 10 个服务器代理作业,它们调用以下过程(每个都有自己的@process_id):

CREATE PROCEDURE sp_do_workorder @process_id INT
AS
BEGIN TRY
    DECLARE @wo_id NCHAR(40),
        @wo_action NVARCHAR(100),
        @created_at DATETIME,
        @source_proc_name NVARCHAR(100),

    UPDATE procedure_ctrl SET [status]='running' WHERE [procedure]='sp_do_workorder_'+CAST(@process_id AS NVARCHAR(100)) AND [status]='idle'

    WHILE 1=1
    BEGIN
        IF NOT EXISTS (SELECT * FROM procedure_ctrl WHERE [procedure]='sp_do_workorder_'+CAST(@process_id AS NVARCHAR(100)) AND [status]='running') BREAK

        SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
        BEGIN TRANSACTION
            UPDATE workorder SET hash=CAST(@process_id AS NVARCHAR(100))
            FROM workorder x
            INNER JOIN (
                SELECT TOP 100 id FROM workorder WHERE hash='' AND workorder_step=0 ORDER BY created_at ASC
            ) y ON x.id=y.id
        COMMIT TRANSACTION
        SET TRANSACTION ISOLATION LEVEL READ COMMITTED

        DECLARE wo_cur CURSOR FAST_FORWARD FOR SELECT id,action,created_at,optin_source FROM workorder WHERE hash=CAST(@process_id AS NVARCHAR(100)) AND workorder_step=0 ORDER BY created_at ASC
        OPEN wo_cur
        FETCH NEXT FROM wo_cur INTO @wo_id,@wo_action,@created_at,@source_proc_name
        WHILE @@FETCH_STATUS=0
        BEGIN
            EXEC sp_basisprozess @wo_id,@wo_action,@created_at,@source_proc_name,@process_id
            FETCH NEXT FROM wo_cur INTO @wo_id,@wo_action,@created_at,@source_proc_name
        END
        CLOSE wo_cur
        DEALLOCATE wo_cur

        WAITFOR DELAY '00:00:01'
    END

    UPDATE procedure_ctrl SET [status]='idle' WHERE [procedure]='sp_do_workorder_'+CAST(@process_id AS NVARCHAR(100)) AND [status]='running'
END TRY
BEGIN CATCH
    EXEC dbo.sp_listerror
    DECLARE @error NVARCHAR(4000)
    SET @error='[sp_do_workorder]_'+CAST(@process_id AS NVARCHAR(100))+': critical problem'
    RAISERROR(@error, 12, 1)    
END CATCH

对于这 10 个代理工作中的大多数,我们确实经常陷入僵局。有没有人暗示为什么会这样?为了防止副作用,我们使用序列化事务隔离级别,因此只有一个代理作业可以获取一个工作订单条目。如果不设置事务隔离,即使死锁消失了,但通常会发生两个(或更多)代理作业获取相同的工作订单条目。

【问题讨论】:

见Using tables as Queues 听起来不错!我会试一试(稍后再提供反馈) 这似乎完全符合我们的需求,非常棒。请回复您的评论。 【参考方案1】:

我没有尝试具体弄清楚为什么会在您的情况下发生死锁,但似乎您正在有效地使用表作为队列,在这种情况下,请参阅this linked article 了解使用OUTPUT 的方法子句和锁定提示,以最大限度地提高并发性而不会出现死锁。

【讨论】:

以上是关于由事务隔离级别分隔的并发进程死锁的主要内容,如果未能解决你的问题,请参考以下文章

数据库事务隔离级别

30分钟全面解析-SQL事务+隔离级别+阻塞+死锁

浅谈MySQL并发控制:隔离级别锁与MVCC

隔离级别

白话Mysql的锁和事务隔离级别!死锁间隙锁你都知道吗?

SQL Server 死锁与事务隔离级别读取未提交