并发更新导致的死锁(使用 SIU 锁)

San*_*apu 5 sql-server deadlock

我们有 2 个不同的 wcf 服务,它们由 Windows 服务调用。这些 wcf 服务中的方法正在更新不同存储过程中的同一个表。有一件事是肯定的:在这两个过程中更新的记录是不同的。这是死锁图xml:

 <deadlock victim="process4cad4c8">
        <process-list>
            <process id="process4cad4c8" taskpriority="0" logused="0" waitresource="PAGE: 11:1:960" waittime="3687" ownerId="1043067366" transactionname="UPDATE" lasttranstarted="2015-01-06T04:01:31.207" XDES="0x19b797950" lockMode="IX" schedulerid="7" kpid="3124" status="suspended" spid="103" sbid="6" ecid="0" priority="0" trancount="2" lastbatchstarted="2015-01-06T04:01:31.197" lastbatchcompleted="2015-01-06T03:53:28.053" clientapp="EntityFramework" hostname="hostname" hostpid="1332" loginname="loginname" isolationlevel="read committed (2)" xactid="1043067366" currentdb="11" lockTimeout="4294967295" clientoption1="673185824" clientoption2="128056">
                <executionStack>
                    <frame procname="DB.schema.Proc1" line="63" stmtstart="5600" stmtend="6756" sqlhandle="0x03000b002bcbd65c9605370018a400000100000000000000">
UPDATE Table1 WITH (ROWLOCK)
    SET Column1 = @Column1
    WHERE Id = @Id    </frame>
                </executionStack>
                <inputbuf>
Proc [Database Id = 11 Object Id = 1557580587]    </inputbuf>
            </process>
            <process id="process8b54c8" taskpriority="0" logused="308" waitresource="KEY: 11:72057594041008128 (6a28efb36b7a)" waittime="3707" ownerId="1043066108" transactionname="user_transaction" lasttranstarted="2015-01-06T04:01:30.750" XDES="0x81bed950" lockMode="X" schedulerid="3" kpid="5420" status="suspended" spid="104" sbid="2" ecid="0" priority="0" trancount="2" lastbatchstarted="2015-01-06T04:01:31.270" lastbatchcompleted="2015-01-06T04:01:31.270" clientapp="EntityFramework" hostname="hostname" hostpid="1332" loginname="loginname" isolationlevel="read committed (2)" xactid="1043066108" currentdb="11" lockTimeout="4294967295" clientoption1="538968096" clientoption2="128056">
                <executionStack>
                    <frame procname="DB.schema.Proc2" line="11" stmtstart="432" stmtend="754" sqlhandle="0x03000b00bac88363cfbb3d0018a400000100000000000000">
UPDATE Table1 WITH (ROWLOCK)
    SET Column1 = @Column1,
    Column2 = @Column2
    WHERE Id = @Id     </frame>
                </executionStack>
                <inputbuf>
Proc [Database Id = 11 Object Id = 1669580986]    </inputbuf>
            </process>
        </process-list>
        <resource-list>
            <pagelock fileid="1" pageid="960" dbid="11" objectname="DB.schema.Table1" id="lockf5976b00" mode="SIU" associatedObjectId="72057594039500800">
                <owner-list>
                    <owner id="process8b54c8" mode="SIU"/>
                </owner-list>
                <waiter-list>
                    <waiter id="process4cad4c8" mode="IX" requestType="convert"/>
                </waiter-list>
            </pagelock>
            <keylock hobtid="72057594041008128" dbid="11" objectname="DB.schema.Table1" indexname="IX_Table1_Column1" id="lock133959780" mode="U" associatedObjectId="72057594041008128">
                <owner-list>
                    <owner id="process4cad4c8" mode="U"/>
                </owner-list>
                <waiter-list>
                    <waiter id="process8b54c8" mode="X" requestType="wait"/>
                </waiter-list>
            </keylock>
        </resource-list>
    </deadlock>
Run Code Online (Sandbox Code Playgroud)

这是我们拥有的索引,它是导致死锁的罪魁祸首之一:

CREATE NONCLUSTERED INDEX [IX_Table1_Column1] ON [schema].[Table1] 
(
    [Column0] ASC,
    [Column1] ASC
)
INCLUDE
( 
    [Id], 
    [Column2]
)
WITH 
(
    PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, SORT_IN_TEMPDB = OFF, 
    IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, 
    ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON, FILLFACTOR = 90
)
ON [PRIMARY];
Run Code Online (Sandbox Code Playgroud)

我们在 column 上有一个主键非聚集索引Id

Pau*_*ite 8

死锁图为:

僵局

这里的具体问题是,(4)IX不兼容与(1) SIU,而这是更常见的兼容IU锁在这种情况下看到。

所述SIU锁的发生是因为与所述选择相关行之间的某处UPDLOCK的提示(可能需要IX在对象级别,IU在页级别,和U在该行电平),一个语句在事务被获取共享(S页面级锁非聚集主键索引结构。

现有的IU页锁被转换为SIU. 虽然共享锁在read committed隔离级别下通常会很快释放,但是当共享锁合并到像SIU. 该锁的生命周期与IU组件相同(此处为事务的生命周期)。

您可以通过确保在语句之后的同一访问方法上不采用页级共享锁来避免这种特定的死锁UPDLOCK(行级共享锁可以)。这将避免SIU页面锁定。页面粒度解释了为什么对两个不同id值的操作会死锁。


也就是说,除非问题缺少一些细节,否则您仍然会有一个更新语句,该语句容易以另一种方式出现死锁。基表似乎是一个堆,更新计划可能会以 RID 查找为特色:

可能的执行计划

这是一个众所周知的模式,在高并发下经常会导致转换死锁。计划的读取端首先访问非聚集索引,然后访问基表。计划的更新端首先访问基表,然后是非聚集索引。通过以相反的顺序访问相同的资源,同一计划的多个并发执行可能会死锁。

查询处理器需要 RID 查找来检查 的值Column1是否正在更改。如果它没有改变,则可以避免冗余的非聚集索引更新。您可以通过包含Column1在非集群主键中,或将主键从非集群主键更改为集群主键来删除此 RID 查找。