i have two hypothetical queries:
UPDATE BankAccounts SET HomePhone = '+1 252-555-0912'
WHERE AccountNumber = 14400000619
and
SELECT * FROM BankAccounts
WHERE HomePhone = '555-1212'
on a hypothetical table with no extra indexes:
CREATE TABLE BankAccounts
(
AccountNumber bigint NOT NULL PRIMARY KEY CLUSTERED,
FirstName nvarchar(50) NOT NULL,
MiddleName nvarchar(50) NULL,
LastName nvarchar(50) NOT NULL,
HomePhone varchar(50) NULL,
IsClosed tinyint DEFAULT 0
)
and everything will be great. If i add an index on HomePhone
:
CREATE INDEX IX_BankAccounts_HomePhone ON BankAccounts
( HomePhone)
Now my SELECT
statement can be a deadlock victim:
Tranasction (Process ID 169) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.
The common suggestions are to:
Except in this case:
What's the long-term solution to eliminate deadlocks like this?
i was considering changing my transaction isolation level to READ UNCOMMITTED
(i.e. eliminating integrity), but because i'm actually dealing with a financial system i'm hesitant to allow a customer to withdraw his entire balance twice.
The only other solution i can find comes from KB Article 83252:
SQL Server technical bulletin - How to resolve a deadlock
...deadlocks cannot be avoided. That is why the front-end application should be designed to handle deadlocks.
In a well designed application, the front-end application should trap for the 1205 error, reconnect to SQL Server, and then re-submit the transaction.
Which i guess is saying: "can't win; don't try"
Anything else?
If you replace the SELECT *
with a column list (or consider all columns) and then add them to the index as INCLUDE
d columns, then the SELECT
query will not need to query the clustered index to complete. The SELECT
can then always run to completion.
If you don't want to do that, then rather than changing the whole isolation level, I'd consider whether a locking hint on the SELECT
is appropriate. Whether the appropriate hint is NOLOCK
or READPAST
is something you'd need to carefully consider (In an ideal world, there would be a way to specify READPAST
but gain information on whether any skipping of rows actually occurred).
Of course, SNAPSHOT
isolation might also be considered (if there are no locks, there are no deadlocks).
(A glutton for punishment might also consider using TABLOCKX
on the SELECT
statement. It'll prevent the deadlock and ensure you read a row if it exists, at a massive penalty for concurrency)