Good reasons to use SELECT... WITH XLOCK?
As far I understand this, I am looking at a KEYLOCK deadlock basically caused by an uncovered index query that uses a nonclustered and a clustered index in order to collect the required values, right?
Essentially, yes. The read operation (select) accesses the nonclustered index first, then the clustered index (lookup). The write operation (insert) accesses the clustered index first, then the nonclustered index. Accessing the same resources in a different order holding incompatible locks can lead to deadlock.
Will drastically reducing the number of rows help here?
It might, because fewer resources are locked and the operation will tend to complete more quickly. If it does help, it may reduce deadlocks, but most likely not eliminate them (but read on).
Is there any good reason I just don't know the SELECT is executed with the XLOCK?
Not really. Locking hints like this are often introduced by people without a full understanding of how isolation, locking, and deadlocks work, in a desperate attempt to reduce or eliminate a problem.
Would the deadlock also happen without XLOCK?
No, if the select actually runs at read uncommitted isolation because incompatible locks won't be taken (and held) in a different order.
Yes, if a locking isolation level is used, and incompatible locks are taken and held in an inconsistent order, for example shared (S) on the nonclustered, then S on the clustered when reading. How likely a deadlock is in this scenario depends on how many locks are taken, and for how long they are held.
Advice
The thing that really stands out (on review) is that the select transaction is running under serializable isolation. That could be being set by your framework, or due to the use of the DTC (Distributed Transaction Coordinator) - see transactionname="DTCXact" in the deadlock graph. You should look into the reasons for this, and look to change it if possible.
Without this escalation to serializable, the chances are very good that this deadlock would not occur, assuming the XLOCK
hint is removed. That said, you would be reading under read uncommitted isolation, which comes with very few consistency guarantees.
If your application and SQL Server code can tolerate reading versions of rows, changing to read committed snapshot isolation (RCSI) or snapshot isolation (SI) for the reads would also avoid the deadlock (XLOCK
removed!), while presenting a consistent, point-in-time view of the committed data. This also assumes you can avoid the serializable isolation, of course.
Ultimately, the XLOCK
hint is counter-productive, but you really need to look into the reason for the use of the serializable isolation level. The trancount = 2
is also interesting - perhaps you are unintentionally nesting transactions here. Something else to check.
- Drastically reducing the number of rows will reduce the likelihood of getting the deadlock, but it won't go away completely.
In simple terms the select is first using the index to determine the rows to select, then fetching the rows, while the insert is inserting a row, then trying to update the (XLOCKED) index.
- Application developers tend to use XLOCK if in the same transaction they want to later do an update on the data. This ensures no one can update the data under them. I would investigate what the application is doing to see if the XLOCK is required.
Having said this, removing the XLOCK probably won't resolve the issue. The SELECT will still take out a shared lock on the index, and the INSERT will want an XLOCK to update it. A shared lock and an XLOCK can't exist on the object together, so you will still get a deadlock. IX_Index1 must be either MyValue or A, or both.
This type of deadlock often occurs because of poorly designed indexes and/or too many indexes. Or poorly written code. Your best option is to see if there is some way the select can be re-written to use another index.