When I chatted with a few senior IT professionals in other organizations a few weeks ago, we happened to touch the topic of TransactionScope. I mentioned that I met a problem a few years back, C# TransactionScope might cause deadlocks and timeouts. To my surprise, they said they also found out and solve this same issue. Although the problem had been solved, it did take us lots of time to discover and solve the problem. So if you use TranscactionScope in your camp, please read on.

The difficulty of finding out and solving this problem was that there were so many factors that can cause deadlocks and timeouts, and how did we know for sure TransactionScope was the most important factor.

I call this the growing pains of TransactionScope. After the developers found out the how easy to use TransactionScope, we began to use it everywhere. I sometimes found that there were too many methods being wrapped and called in one single TransactionScope object. After a few years, there were occasional deadlocks and timeouts shown up. Then as time went by, frequencies of deadlocks and timeouts increased to such a critical point that this problem must be solved.

There were many factors contributing to the deadlocks and timeouts in our case. I listed a few here.

  1. Used TransactionScope everywhere
  2. Put too many methods and/or lines of code into a TransactionScope object
  3. Did not write data access sequentially
  4. Did not configure the isolation level of TransactionScope object

In this blog, I only discuss the #4, did not configure the isolation level of TransactionScope object. The default setting of TransactionScope for database isolation level is serialized. For most scenarios, we should set it to read committed.

In SQL Server SERIALIZABLE transactions are rarely useful and extremely deadlock-prone. Put another way, when the default READ COMMITTED isolation level does not provide the right isolation semantics, SERIALIZABLE is rarely any better and often introduces severe blocking and deadlocking problems. For the difference between serialized and read committed please refer to SQL Book Online (BOL).

In addition, there were blogs and articles discussed about the relationship of TransactionScope and Deadlocks/Timeouts, which also confirmed what we found at that time.

To solve the problem on hand, we used the following code to create a TransactionScope object with the correct read committed isolation level.

    public class TransactionScopeFactory
    {
        public static TransactionScope 
            CreateTransactionScopeWithReadCommittedIsolationLevel()
        {
            TransactionOptions options = new TransactionOptions()
            {
                IsolationLevel = IsolationLevel.ReadCommitted,
                Timeout = TransactionManager.DefaultTimeout
            };
            return new TransactionScope(TransactionScopeOption.Required, options);
        }
    }

The callers would use the following code and get a new correctly configured TransactionScope object.

    using (var scope = TransactionScopeFactory
                       .CreateTransactionScopeWithReadCommittedIsolationLevel())
    {
        // codes here ...
        
        scope.Complete();
    }

 

 

Advertisements