I have a web site that is using Linq2Sql on the server side.
Every now and then (haven’t yet identified the trigger) the whole web site locks up due to an open transaction. I can see the blocking process in master..sysprocesses, from which I can identify that the process ID doing the locking is the IIS process of my web site, open_tran is 1, and using DBCC inputbuffer I can see the query, which is simply a generic select ... from MyTable, which is the SQL generated by Linq2Sql when loading an object. At any rate, it’s certainly not any SQL that I wrote.
I have scanned my entire codebase for any old “BeginTransaction .. Commit/RollbackTransaction” blocks. Nada. Everything uses using (var ts = new TransactionScope()) { ... } blocks. I have attached to the IIS process and caught the Timeout exception when a query in a different thread gets blocked out. There is no other thread I can find hanging around inside a transaction block. Meanwhile the blocking process is not going away at all.
Any ideas on how to troubleshoot this further?
First make sure you investigate the obvious: when such a transaction exists, attach debugger to your ASP process and check all threads, make sure there is no thread blocked/spinning inside a transaction scope. If you use async ADO.Net calls (
.BeginExecuteReader) is more complicated, but given that you use LINQ is very unlikely you use async DB calls.IMHO the most likely cause is a deadlock situation in which the deadlock chain completes inside the client process (in your ASP.Net process). This requires some form of locking in the client (eg. C#’s
lock) and it happens when a thread holding a lock goes into the DB and blocks, while another thread that has the DB lock blocking the first thread is executing in the client wants the hold by the first thread. IS a deadlock, but the chain loop involves alock in SQL Server and another lock in the client process, so it cannot be detected by SQL Server’s deadlock monitor. The very author of this site had run into this issue, so it can happen to anybody. Well, anybody who does expensive-possible-blocking outside calls like DB calls while holding a process lock, which is the first rule of DO NOT DO IT in multithreaded programming… I diverge. Even if you do not have explicit locks in your app, there are compoenents that do have such locks, eg. Log4Net. Attaching the debugger to the process, as recommended above, would show the issue.Another apossible suspect could be transaction enrolement into distributed transaction.
TransactionScopehas this nasty habit of elevating to distributed transaction when multiple connections (eg. multiple Linq data contexts) are used whithin a single transaction scope, see System.Transactions Integration with SQL Server (ADO.NET). A distributed transaction in turn has this nasty property of persisting even after the client committed and disconnected. It has to be acknowledged by the transaction coordinator before SQL Server can actually commit it ‘for good’. When in such a state, you can inspectsys.dm_tran_active_transactionsand it will show up asdtc_state2 (PREPARED). Further investigation, if this turns out to be the case, would require MSDTC troubleshooting.Finaly, a word of caution: why does asimple SELECT hold on to the locks? Are you using the harmful default constructor form
new TransactionScope()? This would use the default SERIALIZABLE isolation level, which is a 99% times overkill and actually harmful. Try using explicitTransactionOptionsand set the isolation level toReadCommitted.