删除和更新之间的死锁

时间:2012-09-28 20:07:08

标签: sql-server-2005 deadlock database-deadlocks

第一个过程:

<deadlock-list>
 <deadlock victim="process8d9798">
  <process-list>
   <process id="process8d9798" taskpriority="0" logused="0" waitresource="PAGE:
    5:1:190354" waittime="3203" ownerId="53807810" transactionname="DELETE" 
    lasttranstarted="11:29:29.153" XDES="0x3dbb518" lockMode="U" 
    schedulerid="2" kpid="1792" status="suspended" spid="57" sbid="0" ecid="1" 
    priority="0" transcount="0" lastbatchstarted="2012-09-28T11:29:29.120" 
    lastbatchcompleted="11:29:29.120" clientapp=".Net SqlClient Data Provider" 
    hostname="xxx" hostpid="4460" isolationlevel="read uncommitted (1)" 
    xactid="53807810" currentdb="5" lockTimeout="4294967295" clientoption1="671088672" 
    clientoption2="128056">
    <executionStack>
     <frame procname="Chooser2.dbo.DeleteUserSelections" line="15" stmtstart="360"
     stmtend="464" sqlhandle="0x030005008839117bf599a500099800000100000000000000">
   DELETE UserPlanOption
    WHERE UserID = @userId     </frame>
    </executionStack>
    <inputbuf>
    </inputbuf>
   </process>

第二个过程:

   <process id="processb84988" taskpriority="0" logused="1744" waitresource="PAGE:
    5:1:190487" waittime="3203" ownerId="53807415" transactionname="user_transaction" 
   lasttranstarted="11:29:13.513" XDES="0x2fc4e6e0" lockMode="IU" 
   schedulerid="4" kpid="4628" status="suspended" spid="52" sbid="0" ecid="0" 
   priority="0" transcount="2" lastbatchstarted="11:29:13.513" 
   lastbatchcompleted="11:29:13.513" 
   clientapp=".Net SqlClient Data Provider" hostname="xxx" 
   hostpid="4460" loginname="chooserpd" isolationlevel="read uncommitted (1)" 
   xactid="53807415" currentdb="5" lockTimeout="4294967295" clientoption1="671088672" 
   clientoption2="128056">
    <executionStack>
     <frame procname="Eligibility" line="149" stmtstart="10566" 
   stmtend="11604" sqlhandle="0x03000500171b4f52c1a6e200ada000000100000000000000">
UPDATE  UserPlanOption 
    SET     RateID = r.ID
    FROM    [User] u WITH (NOLOCK)
        LEFT JOIN Rate r ON r.FamilyTierID = u.FamilyTierID 
    WHERE   UserPlanOption.PlanOptionID NOT IN (SELECT ppo.PlanOptionID FROM 
                    @PORACPlanOptions ppo) AND
        u.ID = @userID AND u.ID = UserPlanOption.UserID AND
        r.PlanOptionID = UserPlanOption.PlanOptionID AND
        r.Criterion1 = dbo.GetPlanOptionAreaID_36()
      </frame>
    </executionStack>
    <inputbuf>
Proc [Database Id = 5 Object Id = 1380915991]    </inputbuf>
   </process>
  </process-list>

资源清单:

  <resource-list>
   <pagelock fileid="1" pageid="190354" dbid="5" objectname="UserPlanOption" 
   id="lock1e482d80" mode="IX" associatedObjectId="72057594060996608">
    <owner-list>
     <owner id="processb84988" mode="IX"/>
    </owner-list>
    <waiter-list>
     <waiter id="process8d9798" mode="U" requestType="wait"/>
    </waiter-list>
   </pagelock>
   <pagelock fileid="1" pageid="190487" dbid="5" objectname="UserPlanOption" 
   id="lock25b32a80" mode="U" associatedObjectId="72057594060996608">
    <owner-list>
     <owner id="process8d9798" mode="U"/>
    </owner-list>
    <waiter-list>
     <waiter id="processb84988" mode="IU" requestType="wait"/>
    </waiter-list>
   </pagelock>
  </resource-list>
 </deadlock>
</deadlock-list>

表“UserPlanOption有一个复合PK(UserId和PlanOptionId)。为什么删除会导致页面锁定?有人可以帮我理解发生了什么吗?让我感到困惑的是,我认为僵局会是由来自同一客户端的后续查询引起,但这是不可能的。这些客户必须是访问同一网页的不同客户端。

实际上,我想我知道第一个问题的答案 - 删除范围将需要页面锁定。但是我怎么能解决这个问题呢?

索引查询的结果:

name                type type_desc is_unique data_space_id ignore_dup_key 
------------------- ---- --------- --------- ------------- -------------- 
PK_UserPlanOption_1 1    CLUSTERED 1         1             0              


is_primary_key is_unique_constraint fill_factor is_padded is_disabled 
-------------- -------------------- ----------- --------- ----------- 
1              0                    0           0         0           

is_hypothetical allow_row_locks allow_page_locks
--------------- --------------- ----------------
0               1               1

updatedelete的执行计划。

2 个答案:

答案 0 :(得分:2)

回答更新

观察

此图表基于XML死锁图: enter image description here

它显示spid52在pageid = 190354(UserPlanOption表)上有一个IX锁定,并在pageid = 190487(来自同一个UserPlanOption表)上请求IU锁定。 我认为UserPlanOption表是一个堆表,意味着它没有集群索引。此外,这意味着您的PK是非聚集的。 如果您将运行此查询:

SELECT i.*
FROM sys.indexes i
WHERE i.object_id = OBJECT_ID('UserPlanOption')

您将获得一个包含UserPlanOption表中所有索引的列表(您可以发布此列表吗?)。在这种情况下,因为spid52执行UPDATE,所以两个锁(IU和IX)在UPDATE执行计划中显示(从我的角度来看)可能的表/索引扫描运算符。

但是,spid57已经在同一资源上有一个U锁(pageid = 190487)。相同的连接(spid57)请求另一个页面上的另一个U锁(pageid = 190354),但此资源(页面)已被spid52(IX)锁定。

因为(见Lock compatibility matrix):

[i]现有的IX和请求的U锁或

[ii]现有的U和请求的IU锁

不兼容你有一个漂亮的死锁。

DELETE语句的缓存计划是 enter image description here

注意:

  • 现在,集群索引扫描(带并行性)操作符强制DBMS扫描UserPlanOption表中的所有行,

  • 估计行数仅为5(估计删除的行数),并且存在 * Parallelism 运算符表明UserPlanOption表很大,

  • 您可以从SQL Server中看到索引建议。

UPDATE语句的缓存计划是 enter image description here

此计划的主要问题是:群集上的群集索引扫描,具有从[N][VAR]CHAR(?)到INT的隐式转换的计算标量以及JOIN之前的过滤器使用用户表。

解决方案

基于这些观察,解决方案应该是:

[1] CREATE INDEX IN_UserPlanOption_UserID_PlanOptionID ON UserPlanOption(UserID,PlanOptionID);

-- SQL Server's suggestion
CREATE INDEX IN_UserPlanOption_UserID
ON UserPlanOption(UserID)
INCLUDE(PlanOptionID); -- optional

注1:在我的选项中,UserPlanOption表上的聚簇索引扫描(DELETE)是导致此死锁的主要原因。

注意2:UserPlanOption在(PlanOptionID, UserID)列上有一个聚簇索引。此索引有助于UPDATE语句(请参阅PK_UserPlanOption_1上的Seek运算符:WHERE ... AND u.ID = UserPlanOption.UserID AND r.PlanOptionID = UserPlanOption.PlanOptionID AND ...),而不是DELETE语句(WHERE UserID=@UserID)。

[2]为了提高UPDATE语句的性能,您可以创建一个sugested索引:

-- SQL Server's suggestion
CREATE INDEX IN_Rate_FamilyTierID
ON dbo.Rate(FamilyTierID)
INCLUDE (PlanOptionID, Criterion1);

[3]要删除隐式转换,可以重写DELETE语句:

DECLARE @Criterion1 Criterion1_datatype? 
SET @Criterion1 = dbo.GetPlanOptionAreaID_36()

UPDATE  UserPlanOption 
    SET     RateID = r.ID
    FROM    [User] u 
        LEFT JOIN Rate r ON r.FamilyTierID = u.FamilyTierID 
    WHERE   UserPlanOption.PlanOptionID NOT IN (SELECT ppo.PlanOptionID FROM 
                    @PORACPlanOptions ppo) AND
        u.ID = @userID AND u.ID = UserPlanOption.UserID AND
        r.PlanOptionID = UserPlanOption.PlanOptionID AND
        r.Criterion1 = @Criterion1

原始源代码(UPDATE语句)包含此过滤器r.Criterion = dbo.GetPlanOptionAreaID_36(...)。此时, if 从Rate表中为每一行调用此函数,那么Computer Scalar运算符可能是另一个性能问题。

这个功能是否具有确定性功能?

SELECT  r.IS_DETERMINISTIC, r.*
FROM    INFORMATION_SCHEMA.ROUTINES r
WHERE   r.ROUTINE_NAME='GetPlanOptionAreaID_36'

[4]我的建议是不要使用NOLOCK提示和/或SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED。此解决方案应该只是最后一个解决方案

答案 1 :(得分:1)

我认为死锁的原因是由于UserPlanOption表以不同的顺序访问,很可能是由于FamilyTierId列上的索引存在。

您可以详细了解这类问题herehere

避免此死锁情况的一种可能方法是在UPdate语句之前根据UserID列预先获取锁定,如

SELECT @userId = UserId FROM UserPlanOption WITH(UPDLOCK)     WHERE UserID = @userId