Strange Deadlock issue

Hi,
Before posting this thread, I have searched for answer in this forum.
But this case is a little bit different from the others.

In our application, we encounter deadlock on a single table.
Env: SQLServer 2000, SP4, ReadCommitted

One thread is trying to do this update sql:
"update resources_allocate set FromDate=?, RepFromDate=?, ResId=?, Allocation=?, Manday=?, RepManday=?, ObjId=?, ObjType=?, ToDate=?, RepToDate=?, Description=?, Status=?, RequestTs=?, FrozenAllocation=?, FrozenToDate=?, FrozenFromDate=?, ApprAllocation=?, ApprToDate=?, ApprFromDate=?, RequestUserId=? where allocationId=?"
The criteria allocationId is the PK. So it is going to update one record.

The other thread is trying to do this select sql:
"select resourcesa0_.allocationId as allocati1_, resourcesa0_.FromDate as FromDate126_, resourcesa0_.RepFromDate as RepFromD3_126_, resourcesa0_.ResId as ResId126_, resourcesa0_.Allocation as Allocation126_, resourcesa0_.Manday as Manday126_, resourcesa0_.RepManday as RepManday126_, resourcesa0_.ObjId as ObjId126_, resourcesa0_.ObjType as ObjType126_, resourcesa0_.ToDate as ToDate126_, resourcesa0_.RepToDate as RepToDate126_, resourcesa0_.Description as Descrip12_126_, resourcesa0_.Status as Status126_, resourcesa0_.RequestTs as RequestTs126_, resourcesa0_.FrozenAllocation as FrozenA15_126_, resourcesa0_.FrozenToDate as FrozenT16_126_, resourcesa0_.FrozenFromDate as FrozenF17_126_, resourcesa0_.ApprAllocation as ApprAll18_126_, resourcesa0_.ApprToDate as ApprToDate126_, resourcesa0_.ApprFromDate as ApprFro20_126_, resourcesa0_.RequestUserId as Request21_126_ from resources_allocate resourcesa0_ where (objId=? )and(objType=? ) order by resourcesa0_.FromDate , resourcesa0_.ToDate"
The resultset is going to be a multiple one.

And this is the 1204 deadlock trace:
2005-10-20 11:05:37.50 spid4 Deadlock encountered .... Printing deadlock information
2005-10-20 11:05:37.50 spid4
2005-10-20 11:05:37.50 spid4 Wait-for graph
2005-10-20 11:05:37.50 spid4
2005-10-20 11:05:37.50 spid4 Node:1
2005-10-20 11:05:37.51 spid4 PAG: 27:1:13410 CleanCnt:2 Mode: IX Flags: 0x2
2005-10-20 11:05:37.51 spid4 Grant List 0::
2005-10-20 11:05:37.51 spid4 Owner:0x1bee52c0 Mode: IX Flg:0x0 Ref:4 Life:02000000 SPID:52 ECID:0
2005-10-20 11:05:37.51 spid4 SPID: 52 ECID: 0 Statement Type: UPDATE Line #: 1
2005-10-20 11:05:37.51 spid4 Input Buf: RPC Event: sp_executesql;1
2005-10-20 11:05:37.51 spid4 Requested By:
2005-10-20 11:05:37.51 spid4 ResType:LockOwner Stype:'OR' Mode: S SPID:51 ECID:0 Ec:(0x68565518) Value:0x335d6900 Cost:(0/0)
2005-10-20 11:05:37.51 spid4
2005-10-20 11:05:37.51 spid4 Node:2
2005-10-20 11:05:37.51 spid4 PAG: 27:1:13409 CleanCnt:2 Mode: S Flags: 0x2
2005-10-20 11:05:37.51 spid4 Grant List 0::
2005-10-20 11:05:37.51 spid4 Owner:0x67b339c0 Mode: S Flg:0x0 Ref:1 Life:00000000 SPID:51 ECID:0
2005-10-20 11:05:37.53 spid4 SPID: 51 ECID: 0 Statement Type: SELECT Line #: 1
2005-10-20 11:05:37.53 spid4 Input Buf: RPC Event: sp_execute;1
2005-10-20 11:05:37.53 spid4 Grant List 1::
2005-10-20 11:05:37.53 spid4 Requested By:
2005-10-20 11:05:37.53 spid4 ResType:LockOwner Stype:'OR' Mode: IX SPID:52 ECID:0 Ec:(0x1E1E1518) Value:0x67ca25c0 Cost:(0/2B2C)
2005-10-20 11:05:37.53 spid4 Victim Resource Owner:
2005-10-20 11:05:37.53 spid4 ResType:LockOwner Stype:'OR' Mode: S SPID:51 ECID:0 Ec:(0x68565518) Value:0x335d6900 Cost:(0/0)
Error: 1205, Severity: 13, State: 61

I just can't figure out why it causes deadlock.
Any one can shed some light on?

Regards,
AlexQiu

[3700 byte] By [alexqiu] at [2007-12-17]
# 1
From your deadlock graph:

Session-52 (updater) has IX lock on page 13410 and requesting an IX lock on page 13409

Session-51 (select) has S lock on 13409 and requesting an S lock on 13410.

So there is a deadlock. Couple of observations:
(1) It appears that the locking granularity for session-51 is being chosen as PAGE. Try running with ROWLOCK hint. this will prevent session-51 from taking PAGE locks

(2) Session-52 is trying to access two pages with IX lock. I don't understand this. Assuming that the update is using the unique index, it should only need to access one data page. Can you please do a show plan on this to see what index is being used. I will be curious to know what other indexes you have on the table? I will also suggest to do DBCC PAGE on the two pages.

thanks

SunilAgarwal at 2007-9-9 > top of Msdn Tech,SQL Server,SQL Server Database Engine...
# 2

Many thanks for your reply.

The execution plan for Session-52 is:
Execution Tree
--
Assert(WHERE:(If ([Expr1007]<>'Job' AND [Expr1007]<>'CO') then 0 else If ((((([Expr1011]<>'Replace' AND [Expr1011]<>'Reject') AND [Expr1011]<>'Request') AND [Expr1011]<>'Plan') AND [Expr1011]<>'Cancel') AND [Expr1011]<>'Approve') then 1 else If (NOT([Pass1022]) AND ([Expr1021] IS NULL)) then 2 else NULL))
|--Nested Loops(Left Semi Join, WHERE:([@3] IS NULL), DEFINE:([Expr1021] = [PROBE VALUE]))
|--Clustered Index Update(OBJECT:([alexqiu].[dbo].[resources_allocate].[PK__resources_alloca__58B39691]), SET:([resources_allocate].[ToDate]=RaiseIfNull([Expr1008]), [resources_allocate].[ResId]=RaiseIfNull([@3]), [resources_allocate].[RequestUserId]=[@20], [resources_allocate].[RequestTS]=[Expr1012], [resources_allocate].[RepToDate]=[Expr1009], [resources_allocate].[RepManday]=[@6], [resources_allocate].[RepFromDate]=[Expr1005], [resources_allocate].[ObjId]=RaiseIfNull([@7]), [resources_allocate].[Manday]=RaiseIfNull([@5]), [resources_allocate].[ApprToDate]=[Expr1017], [resources_allocate].[ApprFromDate]=[Expr1018], [resources_allocate].[ApprAllocation]=[Expr1016], [resources_allocate].[FrozenToDate]=[Expr1014], [resources_allocate].[FrozenFromDate]=[Expr1015], [resources_allocate].[FrozenAllocation]=[Expr1013], [resources_allocate].[FromDate]=RaiseIfNull([Expr1004]), [resources_allocate].[Allocation]=RaiseIfNull([Expr1006]), [resources_allocate].[Description]=[Expr1010], [resources_allocate].[ObjType]=RaiseIfNull([Expr1007]), [resources_allocate].[Status]=RaiseIfNull([Expr1011])), DEFINE:([Expr1004]=Convert([@1]), [Expr1005]=Convert([@2]), [Expr1006]=Convert([@4]), [Expr1007]=Convert([@8]), [Expr1008]=Convert([@9]), [Expr1009]=Convert([@10]), [Expr1010]=Convert([@11]), [Expr1011]=Convert([@12]), [Expr1012]=Convert([@13]), [Expr1013]=Convert([@14]), [Expr1014]=Convert([@15]), [Expr1015]=Convert([@16]), [Expr1016]=Convert([@17]), [Expr1017]=Convert([@18]), [Expr1018]=Convert([@19])), WHERE:([resources_allocate].[AllocationId]=Convert([@21])))
|--Clustered Index Seek(OBJECT:([alexqiu].[dbo].[resources].[PK__resources__5BC50D66]), SEEK:([resources].[ResId]=[@3]) ORDERED FORWARD)

The other indexes in this table are:
CREATE INDEX resouces_allocate_idx1 ON resources_allocate(ResId, FromDate, ToDate);

CREATE INDEX resouces_allocate_idx2 ON resources_allocate(ObjType, ObjId);

I am going to redo the performance test and use DBCC on the deadlock page.
Thanks again.

Regards,
AlexQiu

alexqiu at 2007-9-9 > top of Msdn Tech,SQL Server,SQL Server Database Engine...
# 3
Update information.
I double check the log.
The same update statement is performed multiple times on different allocationId (PK).

That is:
update resources_allocate set xxx= ... where allocationId = 1xxx
update resources_allocate set xxx= ... where allocationId = 2xxx
update resources_allocate set xxx= ... where allocationId = 3xxx

So IX locks on different page.

But I run the select myself in Query Analyzer, I just get the IS lock on the page.
IS lock and IX lock is compatible.

How can I reproduce the S lock on the page in a select?

alexqiu at 2007-9-9 > top of Msdn Tech,SQL Server,SQL Server Database Engine...
# 4
Hi,
Maybe I get the answer.
The S lock is the result of lock escalation.
When many IS locks on a page, SQLServer escalates the lock to S lock to reduce the locks overhead. That's why I get S lock in a select statement, right?

So the deadlock is generated somehow like this process:
Two records, says: A, B
1. System put IS locks on A, B because of a select statement.
2. At the same time, A get an IX lock by the update statement. (IS, IX are compatible)
3. Then system decides to escalate the IS locks to S locks because of the overhead. And upgrade the IS lock on B to S lock.
4. Anohter update statement comes, try to gain an IX lock on B but failed. (IX and S are not compatible)
5. System tries to upgrade the IS lock on A to S lock but failed. (Waiting for the update statement to release the IX lock)

If it is true, how can I prevent this kind of deadlock?
Your suggestion is to use Rowlock hint to resolve it. But the situation is we are now using an ORMapping tools to deal with DB (Hiberxxxx), the rowlock implementation on sqlserver is not yet implemented, so I just want to see whether there is another work around.

Sorry for ocupy your time, many thanks again for your reply.

Regards,
AlexQiu

alexqiu at 2007-9-9 > top of Msdn Tech,SQL Server,SQL Server Database Engine...
# 5

SQL Server only escalates locks from row to table or page to table. It never escalates row lock to page. When SQL Server runs a query, it tries to figure out what should be the locking granularity (note this is different from lock escalation). What I am suspecting is that SQL Sever is choosing Page level locking granularity for your select statement. Now, I did not understand what you meant by 'rowlock implementation on SQL Server is not yet implemented?". My suggestion is to use ROWLOCK hint on your select statement to prevent SQL Server to choose row level granularity.

It seems that you are running multiple updates in one transaction? If yes, then it explains why you are seeing two IX locks on two different pages. Your update plan shows nested loop join. Do you know why it is so? Is your target table a base table or a view?
Thanks

SunilAgarwal at 2007-9-9 > top of Msdn Tech,SQL Server,SQL Server Database Engine...
# 6

Thanks for your reply.

You are right, SQLServer choose Page S lock for my select statement.
I can't reproduce it in the Query window. (Only IS lock on page)

The "rowlock implementation on SQL Server is not yet implemented" means Hiberxxxx not yet implements the 'rowlock hint' on SQLServer thru its Hql & API.

So the solution becomes writing native sql like "Select ... with ROWLOCK where...". (It resolves the deadlock issue, I have tested. Thanks.)

But this makes our application db dependent, it seems not so good. I just want to see if there is any other solution.

Yes, there are multiple updates in the app. But I don't know about the nested loop join. (The cluster index update is the PK). The target table is a base table.

Here is the whole table definition. Hope it helps.
==========================================================
CREATE TABLE resources_allocate (
Allocation tinyint NOT NULL,
AllocationId id PRIMARY KEY,
Description string3,
FromDate ts,
FrozenAllocation tinyint,
FrozenFromDate ts NULL,
FrozenToDate ts NULL,
ApprAllocation tinyint,
ApprFromDate ts NULL,
ApprToDate ts NULL,
Manday int NOT NULL,
ObjId id,
ObjType type CHECK (ObjType IN ('CO', 'Job')),
RepFromDate ts NULL,
RepManday int,
RepToDate ts NULL,
RequestTS ts NULL,
RequestUserId id NULL,
ResId id REFERENCES resources ON DELETE CASCADE,
Status type CHECK (Status IN ('Approve','Cancel', 'Plan', 'Request', 'Reject', 'Replace')),
ToDate ts
);

CREATE INDEX resouces_allocate_idx1 ON resources_allocate(ResId, FromDate, ToDate);

CREATE INDEX resouces_allocate_idx2 ON resources_allocate(ObjType, ObjId);
============================================================

Thanks again.

Regards,
AlexQiu

alexqiu at 2007-9-9 > top of Msdn Tech,SQL Server,SQL Server Database Engine...
# 7
Oh, you don't need to change your application. you can use sp_indexoption SP and set it disallowpagelocks. You can use it to control at a table or index level.
thanks
SunilAgarwal at 2007-9-9 > top of Msdn Tech,SQL Server,SQL Server Database Engine...

SQL Server

Site Classified