Linq to SQL solve Transaction deadlock and Query timeout problem using uncommitted reads

When your database tables start accumulating thousands of rows and many users start working on the same table concurrently, SELECT queries on the tables start producing lock contentions and transaction deadlocks. This is a common problem in any high volume website. As soon as you start getting several concurrent users hitting your website that results in SELECT queries on some large table like aspnet_users table that are also being updated very frequently, you end up having one of these errors:

Transaction (Process ID ##) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.

Or,

Timeout Expired. The Timeout Period Elapsed Prior To Completion Of The Operation Or The Server Is Not Responding.

The solution to these problems are – use proper index on the table and use transaction isolation level Read Uncommitted or WITH (NOLOCK) in your SELECT queries. So, if you had a query like this:

SELECT * FORM aspnet_users 
where ApplicationID =’xxx’ AND LoweredUserName = 'someuser'

You should end up having any of the above errors under high load. There are two ways to solve this:

SET TRANSACTION LEVEL READ UNCOMMITTED;
SELECT * FROM aspnet_Users 
WHERE ApplicationID =’xxx’ AND LoweredUserName = 'someuser'

Or use the WITH (NOLOCK):

SELECT * FROM aspnet_Users WITH (NOLOCK) 
WHERE ApplicationID =’xxx’ AND LoweredUserName = 'someuser'

The reason for the errors are that since aspnet_users is a high read and high write table, during read, the table is partially locked and during write, it is also locked. So, when the locks overlap on each other from several queries and especially when there’s a query that’s trying to read a large number of rows and thus locking large number of rows, some of the queries either timeout or produce deadlocks.

Linq to Sql does not produce queries with the WITH (NOLOCK) option nor does it use READ UNCOMMITTED. So, if you are using Linq to SQL queries, you are going to end up with any of these problems on production pretty soon when your site becomes highly popular.

For example, here’s a very simple query:

using (var db = new DropthingsDataContext())
{
    var user = db.aspnet_Users.First();
    var pages = user.Pages.ToList();
}

DropthingsDataContext is a DataContext built from Dropthings database.

When you attach SQL Profiler, you get this:

image

You see none of the queries have READ UNCOMMITTED or WITH (NOLOCK).

The fix is to do this:

using (var db = new DropthingsDataContext2())
{
    db.Connection.Open();
    db.ExecuteCommand("SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;");

    var user = db.aspnet_Users.First();
    var pages = user.Pages.ToList();
}

This will result in the following profiler output

image

As you see, both queries execute within the same connection and the isolation level is set before the queries execute. So, both queries enjoy the isolation level.

Now there’s a catch, the connection does not close. This seems to be a bug in the DataContext that when it is disposed, it does not dispose the connection it is holding onto.

In order to solve this, I have made a child class of the DropthingsDataContext named DropthingsDataContext2 which overrides the Dispose method and closes the connection.

   class DropthingsDataContext2 : DropthingsDataContext, IDisposable
    {
        public new void Dispose()
        {
            if (base.Connection != null)
                if (base.Connection.State != System.Data.ConnectionState.Closed)
                {
                    base.Connection.Close();
                    base.Connection.Dispose();
                }

            base.Dispose();            
        }
    }

This solved the connection problem.

There you have it, no more transaction deadlock or lock contention from Linq to SQL queries. But remember, this is only to eliminate such problems when your database already has the right indexes. If you do not have the proper index, then you will end up having lock contention and query timeouts anyway.

There’s one more catch, READ UNCOMMITTED will return rows from transactions that have not completed yet. So, you might be reading rows from transactions that will rollback. Since that’s generally an exceptional scenario, you are more or less safe with uncommitted read, but not for financial applications where transaction rollback is a common scenario. In such case, go for committed read or repeatable read.

There’s another way you can achieve the same, which seems to work, that is using .NET Transactions. Here’s the code snippet:

using (var transaction = new TransactionScope(
    TransactionScopeOption.RequiresNew,
    new TransactionOptions()
    {
        IsolationLevel = IsolationLevel.ReadUncommitted,
        Timeout = TimeSpan.FromSeconds(30)
    }))
{
    using (var db = new DropthingsDataContext())
    {
        var user = db.aspnet_Users.First();
        var pages = user.Pages.ToList();

        transaction.Complete();
    }
}

Profiler shows a transaction begins and ends:

image

The downside is it wraps your calls in a transaction. So, you are unnecessarily creating transactions even for SELECT operations. When you do this hundred times per second on a web application, it’s a significant over head.

Some really good examples of deadlocks are given in this article:

http://www.code-magazine.com/article.aspx?quickid=0309101&page=2

I highly recommend it.

kick it on DotNetKicks.com
Published Sat, Mar 7 2009 7:30 by omar

Comments

# re: Linq to SQL solve Transaction deadlock and Query timeout problem using uncommitted reads

Saturday, March 07, 2009 4:40 AM by Javed Hasan

>>>Linq to Sql does not produce queries with the WITH (NOLOCK) option nor does it use READ UNCOMMITTED

This is a bad example of playing with isolation level, there are already correct objects in .NET Framework to achive this:

So instead of:

db.ExecuteCommand("SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;");

Use:

using (new TransactionScope(TransactionScopeOption.Required, new TransactionOptions { IsolationLevel = IsolationLevel.ReadUncommitted }))

{

//Your db Operation

}

Check: www.hanselman.com/.../GettingLINQToSQLAndLINQToEntitiesToUseNOLOCK.aspx

Pls search the internet prior submitting this kind of post.

Next, not sure about the Dispose Bug of DataContext, as reflector shows that it is calling the internal provider Dispose method which in turn calls the ConnectionManger DisposeConnection Method.

# re: Linq to SQL solve Transaction deadlock and Query timeout problem using uncommitted reads

Saturday, March 07, 2009 7:27 AM by omar

System.Transactions has significant overhead. I have never been able to use it on a high volume website without making CPU go 100% and Req/sec come down to 1/10th. It's made for enterprise applications, not for high volume websites.

# re: Linq to SQL solve Transaction deadlock and Query timeout problem using uncommitted reads

Saturday, March 07, 2009 7:28 AM by omar

Also I saw the post from ScottH, and I tried it, and it made my web site go down. As I said, System.Transactions is for Enterprise Apps, not for high volume web apps.

# re: Linq to SQL solve Transaction deadlock and Query timeout problem using uncommitted reads

Saturday, March 07, 2009 8:30 AM by Javed Hasan

Dont agree, as per MSDN:

"In addition to the programmability enhancements, System.Transactions and ADO.NET can work together to coordinate optimizations when working with transactions. A promotable transaction is a lightweight (local) transaction that can be automatically promoted to a fully distributed transaction on an as-needed basis.

In ADO.NET 2.0, System.Data.SqlClient adds support for promotable transactions when working with SQL Server 2005. A promotable transaction does not invoke the added overhead of a distributed transaction unless the added overhead is required. Promotable transactions are automatic require no intervention from the developer."

Maybe you have other issues in your design/architecture. Try consult with SO guys they are running on L2S and Does not have this issue.

# re: Linq to SQL solve Transaction deadlock and Query timeout problem using uncommitted reads

Saturday, March 07, 2009 8:32 AM by Javed Hasan

In either case you should not claim that L2S does not has the support. "Linq to Sql does not produce queries with the WITH (NOLOCK) option nor does it use READ UNCOMMITTED"

Correct your post or mentioned whatever your findings with TransactionScope otherwise it conveys wrong message to the community.

# re: Linq to SQL solve Transaction deadlock and Query timeout problem using uncommitted reads

Saturday, March 07, 2009 8:46 AM by omar

Dude, look at the profiler trace. System Transaction is unnecessarily opening a transaction where I am just doing a SELECT! Can't you see the difference between the different profiler traces?

What you are showing me here is Distributed Transaction. Even local System.Transaction brings a high volume site down, let alone distributed transaction. You never, I repeat, never use DTC on a web app. It's not made for web apps. I know what I am taking about because I have tested this in a real project. Don't just read text books. Build a sample web site, do a load test and see yourself.

Linq to Sql does not produce queries with the WITH (NOLOCK) option nor does it use READ UNCOMMITTED. It's the System.Transaction that opens a transaction in READ UNCOMMITTED mode. L2S has no credit here. Dude, read the MSDN!

# re: Linq to SQL solve Transaction deadlock and Query timeout problem using uncommitted reads

Saturday, March 07, 2009 9:58 AM by Shamol

I have a production database with a table of 100,000 rows. Simple update query like

"Update Table X set x.Field=2

where Id=2 "

started failing. Looked into the Index it was highly fragmented. i optimized it but it became fragmented quickly. Then i just drop that table keeping a backup and created a new table with the same structure and dumped the data it ran fine. i wonder why would that happen any ideas?

Oh my table was a dynamically created by my application.

# re: Linq to SQL solve Transaction deadlock and Query timeout problem using uncommitted reads

Saturday, March 07, 2009 10:11 AM by omar

Can you send the CREATE TABLE script, the indexes and some sample queries?

Looks like you need some weekly REINDEX job on the table which basically drops clustered index and recreates it.

# re: Linq to SQL solve Transaction deadlock and Query timeout problem using uncommitted reads

Saturday, March 07, 2009 10:51 AM by Javed Hasan

No I dont see anything in Profiler opening a transaction:

set quoted_identifier on

set arithabort off

set numeric_roundabort off

set ansi_warnings on

set ansi_padding on

set ansi_nulls on

set concat_null_yields_null on

set cursor_close_on_commit off

set implicit_transactions off

set language us_english

set dateformat mdy

set datefirst 7

set transaction isolation level read uncommitted

Again it is not a DTC it is just setting the transaction isolation level, no overhead at all as what you are doing it does progrmatically with no string command and hacks.

I have enough of this nonsense.

Regarding your philoshopy with L2S with System.Transaction reminds me

"I dont eat my mouth eats"

# re: Linq to SQL solve Transaction deadlock and Query timeout problem using uncommitted reads

Monday, March 09, 2009 5:45 PM by Ray Fan

Dear Omar,

This is a very practical post.  After reading the comments above I did some research and found that others use and recommend "Snapshot Isolation" if you use SqlServer 2005 or later.

For example, Jeff encounter the same problem on his StackOverflow.com, so he posted this same question on StackOverflow at stackoverflow.com/.../diagnosing-deadlocks-in-sql-server-2005

And, it seems like after feedback, he decides to use the following

ALTER Database [StackOverflow.Beta] SET READ_COMMITTED_SNAPSHOT ON

Then I find this MSDN article on the theory and implementation on Snapshot Isolation at msdn.microsoft.com/.../tcbchxcb(VS.80).aspx

I'd like to ask if you already knew and tried this Snapshot approach and if yes, did you also find it de-grade the performance of your site?

If you haven't tried this method, do you mind try it out and let us know if it works for you?

Thanks,

Ray.

# Linq to SQL solve Transaction deadlock and Query timeout problem using uncommitted reads

Sunday, October 11, 2009 8:05 PM by PimpThisBlog.com

Thank you for submitting this cool story - Trackback from PimpThisBlog.com

# Cheap tramadol.

Tuesday, December 29, 2009 9:49 PM by Cheap tramadol without prescription.

Cheap tramadol. Tramadol. Tramadol hcl.

Leave a Comment

(required) 
(required) 
(optional)
(required)