A transaction places locks on all rows touched with the connection the transaction is ran over.
So in the code above: you start a transaction, then you read two rows. This places read locks on the rows. You then save the rows, which place write locks on the rows. then the transaction commits and the locks are freed.
If you read the rows first, then save 1 entity, then suspend the transaction, you will likely run into problems, as the other thread cant' write the data. It will wait for the read locks to be lifted and the write lock on one row. As the other is suspended, everything deadlocks.
Rule of thumb:
- read data first
- then start transaction
- only WRITE data during transactions
- commit early
Do not fetch data during a transaction unless the transaction is very quick, but it's not recommended. If you need data to perform changes on, fetch it FIRST, perform the changes, THEN write the data.
Repeatable read is like serializable an isolation level which holds read locks till the end of the transaction. ReadCommitted doesn't do this, it lifts readlocks right away. So this can be the problem: the reads inside the transaction.
// create adapter for fetching and the transaction.
using(DataAccessAdapter adapter = new DataAccessAdapter())
{
for(int i=0; i < 2; i++)
{
// fetch the two entities
CustomerEntity customer = new CustomerEntity(1);
ContactEntity contact = new ContactEntity(1);
adapter.FetchEntity(customer);
adapter.FetchEntity(contact);
// start the transaction.
customer.TerritoryId += 1;
customer.ModifiedDate = DateTime.Now;
contact.FirstName = "Console";
try
{
adapter.StartTransaction(IsolationLevel.ReadCommitted, "TwoDebug");
adapter.SaveEntity(customer);
adapter.SaveEntity(contact);
adapter.Commit();
}
catch
{
adapter.RollBack();
throw;
}
}
}
Btw, you HAVE TO use two separate threads to test multi-user. You can't use a loop.
If there's discussion about whether the reads should be INSIDE the transaction: all data placed outside the db is by definition stale. So this is perfectly acceptable. You can try SqlServer's Snapshot isolation level, but I think that's also not really good. If you really must follow the paths you outlined so read inside a transaction, use oracle.