Sorry, I don't have code compact enough to post it here. But consider this situation:
Two clients are accessing our service app at the same time. Inside the service two threads are started (managed by IIS). Client 1 (thread 1) starts a transaction and executes a SELECT statement for record 123. Client 2 (thread 2) starts a transaction and also executes a SELECT statement for record 123. This is working fine even for isolation level REPEATABLE READ, because the SELECT statements places only shared locks (READ LOCKs) on the record.
If now thread 1 trys to execute an UPDATE statement, the UPDATE waits until transaction 2 is completed either with commit or rollback. But if thread 2 now starts also an UPDATE statement, we have a deadlock situation. This because the UPDATE commands try to place exclusive locks (UPDATE LOCKs) on the record and have to wait until all other LOCKS (shared and execlusive) are released.
Here is a short code snipped which demonstrates this situation:
public void WebServiceMethod()
{
DataAccessAdapter adapter = new DataAccessAdapter(true);
adapter.StartTransaction(IsolationLevel.RepeatableRead, "Test");
try
{
CustomerEntity customer = new CustomerEntity(123);
adapter.FetchEntity(customer);
/* some time consuming processing comes here */
customer.Name = "Test";
adapter.SaveEntity(customer, false, null, false);
adapter.Commit();
}
catch
{
adapter.Rollback();
throw;
}
finally
{
adapter.Dispose();
adapter = null;
}
}
As you can see, the chance to create a deadlock is very high, if you call this method twice or more times at the same time.
One solution would be to reduce the isolation level. But this is forbidden by our business rules. Only one client should have access to a single record at the same time!
The onliest solution I found is to place exclusive locks (UPDATE LOCKs) together with the SELECT statements, so shared locks (READ LOCKs) will never be used. In SQL Server this is usually done with the table hint WITH(UPDLOCK).