E
Erland Sommarskog
Jonathan said:This code was working just fine all day. But, suddenly, the database query
in the catch block was timing out, causing the second, outter, catch
statement to run.
If I set CommandTimeout to 0, it blocks indefinitely. While it was
blocked, I ran sp_who2 and the results can be seen at
http://www.fileparade.com/DBBlock.jpg.
It shows the UPDATE is blocked by the SELECT. But why does a SELECT block?
It's funny, there is a long thread, but no one has answered one seemingly
puzzling question, and no one has mentioned the solution which might be
the easiest to implement.
Why did this work before, and then stopped working one day? The answer
to this is that although you read one row at a time in your code, the
client API will get a bunch of rows at a time from SQL Server. So as long
as the SqlClient could get all rows in the result set in one go, you were
fine, because by the time you performed the UPDATE the locks from the
SELECT were released.
But the day when the result set grow in size, or something else changed
that caused ADO .Net to get only some of the rows, and now the UPDATE was
blocked, because SQL Server still held the locks, as the SELECT was
still running.
You may ask how to control the buffering in ADO .Net, but I don't think
SqlClient exposes such a knob, and trying to avoid the issue this way
is a dead end anyway.
The simplest solution is probably to employ a feature known as MARS,
Multiple Active Result Sets. MARS was designed exactly for the scenario
you have. With MARS you can retrieve rows from a SELECT query and on the
same connection run UPDATES against the table you are updating. To
use MARS, you need to say something in the connection string, please refer
to MSDN for details.
Just like snapshot isolation, MARS will take a toll on the version store
in tempdb, but since enabling snapshot isolation affects everything that
goes on in the database that has a much wider repercussion than using
MARS.
But before you go ahead and implement MARS, I should say that I'm fairly
lukewarm to this feature. I believe it works very well for a program such
as yours, but MARS offers many possibilties to shoot yourself in the foot.
One thing that is important to understand is that when there are multiple
commands executing on the same connection, they are not executing in
parallel, but the statements are interleaved.
Nevertheless, I think MARS is a better solution than using any of the
snapshot models that were suggested in the thread, and it's definitely
a better choice then READ COMMITTED SNAPSHOT. Both snapshot models
requires settings be made the database owner, and if the database is
later on reconfigured to no longer use snapshot, your program will fail.
If you rely on READ COMMITTED SNAPSHOT being in effect, your program
will just hang. If you issue a SET TRANSACTION ISOLATION LEVEL SNAPSHOT,
your program will at least fail loud and clear if snapshot isolation
has been disabled. On the other hand, there is no risk that anyone
configures MARS out of the picture, but that is solely in your code.
There are still more solutions to consider. One is to look if it is
possible to index the table such that the lock on a row has been released
when you update it. Then again, if some change the index, the program
will fail.
Another possibility is to do the buffering yourself. Read a reasonable
amount of rows into a dataset with the .Fill mehtod. Now you can easily
update the rows on the same connection without using MARS.
Yet another possibility would be to keep the reader as you have now,
but defer the updating until you have done all rows, and then update
them all at the end. If you are on SQL 2008, you would pass the data
for the update in a table-valued parameter; On SQL 2005 you could use
an XML string that you shred in SQL Server.
--
Erland Sommarskog, SQL Server MVP, (e-mail address removed)
Links for SQL Server Books Online:
SQL 2008: http://msdn.microsoft.com/en-us/sqlserver/cc514207.aspx
SQL 2005: http://msdn.microsoft.com/en-us/sqlserver/bb895970.aspx
SQL 2000: http://www.microsoft.com/sql/prodinfo/previousversions/books.mspx