cdonner
cdonner

Reputation: 37668

Deadlock between Delete and Update

First process:

<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>

Second 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:

  <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>

The table "UserPlanOption has a composite PK (UserId and PlanOptionId). Why does the delete cause a page lock? Can someone help me understand what is going on? What confused me for a while was that I thought the deadlock would be caused by subsequent queries from the same client, but that's not possible. These must be different clients hitting the same web page.

Actually, I guess I know the answer to the first question - deleting a range will require a page lock. But how can I get around this?

Results from index query:

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

Execution plans for the update and the delete.

Upvotes: 0

Views: 3484

Answers (2)

Bogdan Sahlean
Bogdan Sahlean

Reputation: 1

Answer updated

Observations

This diagram is based on XML deadlock graph: enter image description here

It shows that spid52 has an IX lock on pageid=190354 (UserPlanOption table) and is requesting an IU lock on pageid=190487 (from the same UserPlanOption table). I think UserPlanOption table is a heap table, meaning that it doesn't has a clusterd index. Also, this means that your PK is nonclustered. If you will run this query:

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

you will get a list with all indices from UserPlanOption table (can you publish this list ?). In this case, because spid52 executes an UPDATE, both locks (IU and IX) shows (from my point of view) a possible Table/Index Scan operator in UPDATE execution plan.

But, spid57 already has an U lock on the same resource (pageid=190487). The same connection (spid57) requests another U lock on another page (pageid=190354) but this resource (page) is already locked by spid52 (IX).

Because (see Lock compatibility matrix):

[i] existing IX and requested U locks or

[ii] existing U and requested IU locks

are not compatible you have a nice deadlock.

The cached plan for DELETE statement is enter image description here

Notes:

  • Now, a Clustered Index Scan (with Parallelism) operator forces the DBMS to scan all rows from the UserPlanOption table,

  • Estimated number of rows is only 5 (estimated number of deleted rows) abd the presence of *Parallelism operators shows that UserPlanOption table is big,

  • You can see an index suggestion from SQL Server.

The cached plan for UPDATE statement is enter image description here

The main problems with this plan are: Clustered Index Scan on Rate table, a Compute Scalar with an implicit conversion from [N][VAR]CHAR (?) to INT and a filter before the JOIN with the User table.

Solutions

Based on these observations the solutions should be:

[ 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

Note 1: In my option, the Clustered Index Scan on UserPlanOption table (DELETE) is the main cause of this deadlock.

Note 2: The UserPlanOption has an clustered index on (PlanOptionID, UserID) columns. This index helps the UPDATE statement (see Seek operator on PK_UserPlanOption_1: WHERE ... AND u.ID = UserPlanOption.UserID AND r.PlanOptionID = UserPlanOption.PlanOptionID AND ...) but not the DELETE statement (WHERE UserID=@UserID).

[ 2 ] To improve the performance of UPDATE statement you could create the sugested index:

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

[ 3 ] To remove the implicit conversion you could rewrite the DELETE statement thus:

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

The original source code (UPDATE statement) includes this filter r.Criterion = dbo.GetPlanOptionAreaID_36(...). At this moment, if this function is called for every row from Rate table then Computer Scalar operator can be another performance problem.

Is this function a deterministic function ?

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

[ 4 ] My advice is to not use NOLOCK hint and/or SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED. This solution should be only the last solution.

Upvotes: 2

Roji P Thomas
Roji P Thomas

Reputation: 550

I believe the reason of the deadlock is due to the fact the UserPlanOption table is accessed in different order, most probably due to the presense of an index on the FamilyTierId column.

You can read more about these kind of issues here and here

One possible way to avoid this deadlock condition is to acquire the locks beforehand based on the UserID column before the UPdate Statement, as in

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

Upvotes: 1

Related Questions