使用 X 和 U 锁在同一张表上发生死锁

Posted

技术标签:

【中文标题】使用 X 和 U 锁在同一张表上发生死锁【英文标题】:Deadlock on same table with X and U lock 【发布时间】:2019-07-02 23:57:01 【问题描述】:

我在 Begin 和 Commit tran 下有一个包含以下两个事务的存储过程。

UPDATE  mytable
SET     UserID = @ToUserID
WHERE   UserID = @UserID 

DELETE  FROM mytable
WHERE   UserID = @UserID 

在运行多次执行的存储过程时,我遇到了死锁。这是死锁图:

<deadlock-list>
    <deadlock victim="process16409057468">
        <process-list>
            <process id="process16409057468" taskpriority="0" logused="912" waitresource="RID: 6:1:2392:152" waittime="3022" ownerId="6283339" transactionname="user_transaction" lasttranstarted="2019-02-08T21:08:24.663" XDES="0x16401b98490" lockMode="U" schedulerid="8" kpid="23924" status="suspended" spid="92" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2019-02-08T21:08:24.667" lastbatchcompleted="2019-02-08T21:08:24.667" lastattention="1900-01-01T00:00:00.667" clientapp=".Net SqlClient Data Provider" hostname="GYAAN" hostpid="5624" loginname="sa" isolationlevel="read uncommitted (1)" xactid="6283339" currentdb="6" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
                <executionStack>
                    <frame procname="mytable" line="377" stmtstart="33320" stmtend="33540" sqlhandle="0x030006004f6bf63211085201eaa9000001000000000000000000000000000000000000000000000000000000">
                        UPDATE  mytable
                        SET     UserID = @ToUserID
                        WHERE   UserID = @UserID      
                    </frame>
                </executionStack>
                <inputbuf>
                    Proc [Database Id = 6 Object Id = 855010127]    
                </inputbuf>
            </process>
            <process id="process163feab3088" taskpriority="0" logused="912" waitresource="RID: 6:1:2392:149" waittime="99" ownerId="6282851" transactionname="user_transaction" lasttranstarted="2019-02-08T21:08:22.107" XDES="0x16401b20490" lockMode="U" schedulerid="3" kpid="33220" status="suspended" spid="81" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2019-02-08T21:08:22.103" lastbatchcompleted="2019-02-08T21:08:22.107" lastattention="1900-01-01T00:00:00.107" clientapp=".Net SqlClient Data Provider" hostname="GYAAN" hostpid="5624" loginname="sa" isolationlevel="read uncommitted (1)" xactid="6282851" currentdb="6" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
                <executionStack>
                    <frame procname="mytable" line="382" stmtstart="33650" stmtend="33848" sqlhandle="0x030006004f6bf63211085201eaa9000001000000000000000000000000000000000000000000000000000000">
                        DELETE  FROM mytable
                        WHERE   UserID = @UserID     
                    </frame>
                </executionStack>
                <inputbuf>
                    Proc [Database Id = 6 Object Id = 855010127]    
                </inputbuf>
            </process>
        </process-list>

        <resource-list>
            <ridlock fileid="1" pageid="2392" dbid="6" objectname="mytable" id="lock164096b7800" mode="X" associatedObjectId="72057594051493888">
                <owner-list>
                    <owner id="process163feab3088" mode="X"/>
                </owner-list>
                <waiter-list>
                    <waiter id="process16409057468" mode="U" requestType="wait"/>
                </waiter-list>
            </ridlock>
            <ridlock fileid="1" pageid="2392" dbid="6" objectname="mytable" id="lock163f7fb2c80" mode="X" associatedObjectId="72057594051493888">
                <owner-list>
                    <owner id="process16409057468" mode="X"/>
                </owner-list>
                <waiter-list>
                    <waiter id="process163feab3088" mode="U" requestType="wait"/>
                </waiter-list>
            </ridlock>
        </resource-list>
    </deadlock>
</deadlock-list>

有人能解释一下导致死锁的原因和原因吗?

我现在很难理解这个 X 和 U 锁定流程。

你能解释一下 X 和 U 卡在死锁之间的流程吗?

【问题讨论】:

我看不出用 ToUserId 替换具有指定 UserId 的每一行的意义。因为,在更新之后,您尝试删除具有先前 UserId 的每一行。这对我没有任何意义,DELETE 查询将删除 0 行。你能解释一下最终的 DELETE 查询吗? 请添加表上索引的详细信息 你的代码真的没有意义。能贴出真正的存储过程体吗? 【参考方案1】:

您没有提供足够的查询详细信息,但您共享的死锁图清楚地表明这是一个 “writer-writer” 由于并行性而导致的死锁,因为所有授予或请求的锁都是XU

<resource-list>
    <ridlock fileid="1" pageid="2392" dbid="6" objectname="xx" id="lock164096b7800" mode="X" associatedObjectId="72057594051493888">
        <owner-list>
            <owner id="process163feab3088" mode="X"/>
        </owner-list>
        <waiter-list>
            <waiter id="process16409057468" mode="U" requestType="wait"/>
        </waiter-list>
    </ridlock>
    <ridlock fileid="1" pageid="2392" dbid="6" objectname="mytable" id="lock163f7fb2c80" mode="X" associatedObjectId="72057594051493888">
        <owner-list>
            <owner id="process16409057468" mode="X"/>
        </owner-list>
        <waiter-list>
            <waiter id="process163feab3088" mode="U" requestType="wait"/>
        </waiter-list>
    </ridlock>
</resource-list>

关于 writer-writer 死锁的重要一点是 SQL Server 持有排他锁直到事务提交,这与默认read committed 隔离级别中的共享锁不同.

如果没有查询详细信息,就无法找出错误的确切原因。通常你需要重构你的查询以避免像

这样的死锁
    SELECT 查询移到事务之外,使其只返回 提交的数据,而不是返回包含修改的数据 可能会回滚。 有时您需要调整查询,以便 SQL Server 不需要 尽可能多地并行化它。 将MAXDOP 提示添加到查询以强制它串行运行将删除 查询内并行死锁的任何更改。

死锁的其他常见原因是当您读取数据并打算稍后通过共享锁来更新或删除它时,以下UPDATE语句无法获取必要的更新锁,因为资源已被阻塞由另一个导致死锁的进程。

要解决此问题,您可以使用WITH (SERIALIZABLE) 选择记录,如下所示

UPDATE  mytable WITH (SERIALIZABLE)
SET     UserID = @ToUserID
WHERE   UserID = @UserID

这将在记录上获取必要的更新锁,并将停止其他进程获取记录上的任何锁(共享/独占),并防止任何死锁。

您还需要查找查询的顺序,错误的顺序可能导致循环死锁。在这种情况下,查询会等待不同事务中的另一个查询完成。

【讨论】:

【参考方案2】:

除了你的例子是更新然后删除相同的记录,这看起来不对,你可以在执行 DDL 之前获取所有需要的 xlock。

select UserID
FROM mytable   with(xlock, holdlock, rowlock)
WHERE UserID  in (@ToUserID, @UserID)

UPDATE  mytable
SET     UserID = @ToUserID
WHERE   UserID = @UserID 

DELETE  FROM mytable
WHERE   UserID = @UserID 

【讨论】:

以上是关于使用 X 和 U 锁在同一张表上发生死锁的主要内容,如果未能解决你的问题,请参考以下文章

更新查询在同一张表上的 Sql 查询死锁

同一张表上的两个“SELECT FOR UPDATE”语句会导致死锁吗?

瞬间截断和插入同一张表

难以在同一张表上创建更新总计查询

实体框架,插入了引用最后一个Id的外键,同时也是同一张表上之前的集合

使用自引用外键的死锁