<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type="text/xsl" href="http://blogs.conchango.com/utility/FeedStylesheets/rss.xsl" media="screen"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:wfw="http://wellformedweb.org/CommentAPI/"><channel><title>Memoirs of a Plumber</title><link>http://blogs.conchango.com/marcinkaluza/default.aspx</link><description>Moans, Groans and Scary-tales...</description><dc:language>en</dc:language><generator>CommunityServer 2.1 SP3 (Build: 20423.1)</generator><item><title>A pint of ACID.</title><link>http://blogs.conchango.com/marcinkaluza/archive/2008/03/05/a-pint-of-acid.aspx</link><pubDate>Wed, 05 Mar 2008 21:05:00 GMT</pubDate><guid isPermaLink="false">e847c0e7-38d9-45c0-b593-56747303e088:10052</guid><dc:creator>marcin.kaluza</dc:creator><slash:comments>0</slash:comments><comments>http://blogs.conchango.com/marcinkaluza/comments/10052.aspx</comments><wfw:commentRss>http://blogs.conchango.com/marcinkaluza/commentrss.aspx?PostID=10052</wfw:commentRss><description>&lt;p&gt;First of all I'd like to assure
non-developers accidentally reading this post that it has nothing to do with lysergic
acid diethylamide, an A-class substance commonly known as LSD or "acid". It's
all about ACID properties of database transactions in general (boring stuff),
and the "I" property and scalability in particular. Now that we have the
legalities behind us let's get back to business.&lt;/p&gt;

&lt;p&gt;According to Wikipedia the "I" in the
acronym has the following meaning:&lt;/p&gt;

&lt;p&gt;&lt;i&gt;Isolation
refers to the ability of the application to make operations in a transaction
appear isolated from all other operations. This means that no operation outside
the transaction can ever see the data in an intermediate state; a bank manager
can see the transferred funds on one account or the other, but never on both -
even if he ran his query while the transfer was still being processed. More
formally, isolation means the transaction history (or schedule) is
serializable. This ability is the constraint which is most frequently relaxed
for performance reasons. &lt;/i&gt;&lt;/p&gt;

&lt;p&gt;The last sentence is of particular interest
as it implies that isolation comes at a cost (concurrency vs. consistency) and
this basic fact prompted me to do some experiments and in result write this post. &lt;/p&gt;

&lt;p&gt;&lt;b&gt;The
Problem&lt;/b&gt;&lt;/p&gt;

&lt;p&gt;The main problem with maintaining isolation
is that resources which are supposed to be isolated have to be locked. As I am
sure you are aware of it locking in general, and in databases in particular, means
lower concurrency and throughput. &lt;/p&gt;

&lt;p&gt;To see exactly how database performance is
affected by concurrent reads and updates I devised a simple "bank" scenario:
while one thread moves money between hypothetical accounts the other tries to
get the total amount of money held in the bank (which has to remain constant).Various
approaches to this problem are the subject of the rest of this post and as
you'll hopefully see they produce very different results. Although the topic
may seem trivial (and rightly so) it represents a wider class of problems where
the same table is read and updated concurrently.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;All
that's needed to test the "bank" scenario is a table defined as follows:&lt;/p&gt;

&lt;p&gt;&lt;a href="http://blogs.conchango.com/photos/conchango_bloggers/picture10050.aspx" target="_blank"&gt;&lt;img src="http://blogs.conchango.com/photos/conchango_bloggers/images/10050/original.aspx" border="0"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With two stored procedures, one for moving
the money and the other which gets the total accumulated:&lt;/p&gt;

&lt;p&gt;&lt;a href="http://blogs.conchango.com/photos/conchango_bloggers/picture10049.aspx" target="_blank"&gt;&lt;img src="http://blogs.conchango.com/photos/conchango_bloggers/images/10049/original.aspx" border="0"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once the table has been populated with
10000 rows (and equal amount of money in each account) I ran the test app to
see how many times I could concurrently execute both stored procedures within
20 seconds and what results would I get (see attachment for the test app and
SQL setup script).&lt;/p&gt;

&lt;p&gt;&lt;b&gt;First
run&lt;/b&gt;&lt;/p&gt;

&lt;p&gt;The first run of the program with default
SQL Server settings (isolation level set to READ_COMMITTED) produced following
results:&lt;/p&gt;

&lt;table border="1" cellpadding="0" cellspacing="0"&gt;
 &lt;tbody&gt;&lt;tr&gt;
  &lt;td&gt;
  &lt;p&gt;&lt;b&gt;Operation&lt;/b&gt;&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p&gt;&lt;b&gt;Total
  # of executions (average of 3 runs)&lt;/b&gt;&lt;/p&gt;
  &lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
  &lt;td&gt;
  &lt;p&gt;Reads (totals retrieved)&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p&gt;1656&lt;/p&gt;
  &lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
  &lt;td&gt;
  &lt;p&gt;Writes (transfers executed)&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p&gt;19300&lt;/p&gt;
  &lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
  &lt;td&gt;
  &lt;p&gt;Inconsistent results&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p&gt;1120&lt;/p&gt;
  &lt;/td&gt;
 &lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;The interesting thing in this case is the
number of inconsistent results, i.e. the database reported incorrect total of
all balances held in our "bank". In spite of the fact that the movement of
money happens within a transaction, the "reader" is clearly able to see "half-committed"
data. In case you were wondering why this happens have a look at the following
table which illustrates the cause of the problem.&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;table border="1" cellpadding="0" cellspacing="0"&gt;
 &lt;tbody&gt;&lt;tr&gt;
  &lt;td&gt;
  &lt;p align="center"&gt;&lt;b&gt;Time&lt;/b&gt;&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p&gt;&lt;b&gt;Reader&lt;/b&gt;&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p&gt;&lt;b&gt;Writer&lt;/b&gt;&lt;/p&gt;
  &lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
  &lt;td&gt;
  &lt;p align="center"&gt;1&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p&gt;Reads row ID=1, gets the balance = 100
  and releases the lock.&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p&gt;&amp;nbsp;&lt;/p&gt;
  &lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
  &lt;td&gt;
  &lt;p align="center"&gt;2&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p&gt;&amp;nbsp;&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p&gt;Updates row ID=1 sets the
  balance=balance-10 (90) and exclusively locks the row.&lt;/p&gt;
  &lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
  &lt;td&gt;
  &lt;p align="center"&gt;3&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p&gt;&amp;nbsp;&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p&gt;Updates row ID=2 sets the
  balance=balance+10 (110) and exclusively locks the row.&lt;/p&gt;
  &lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
  &lt;td&gt;
  &lt;p align="center"&gt;4&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p&gt;&amp;nbsp;&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p&gt;Commits and releases both locks.&lt;/p&gt;
  &lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
  &lt;td&gt;
  &lt;p align="center"&gt;5&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p&gt;Reads the row ID=2, gets the balance of
  110 producing total of 210!&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p&gt;&amp;nbsp;&lt;/p&gt;
  &lt;/td&gt;
 &lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Due to different locking strategies the
reader hits rows updated by the other statement (and quite possibly the other
way around). The reader never reads uncommitted data so in principle the
database obeys the READ_COMMITED isolation level, the end result however may be
far from desired and many people will find it surprising.&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Approach
no 1&lt;/b&gt;&lt;/p&gt;

&lt;p&gt;The first possible approach to produce
consistent results which came to my mind was to set the isolation level for the
GetTotal stored procedure to REPEATABLE READ. In this case the locks are held
on the data for the duration of the transaction in order to prevent updates.
The outcome however was an immediate and somewhat surprising deadlock which the
following table explains:&lt;/p&gt;

&lt;table border="1" cellpadding="0" cellspacing="0"&gt;
 &lt;tbody&gt;&lt;tr&gt;
  &lt;td&gt;
  &lt;p align="center"&gt;&lt;b&gt;Time&lt;/b&gt;&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p&gt;&lt;b&gt;Reader&lt;/b&gt;&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p&gt;&lt;b&gt;Writer&lt;/b&gt;&lt;/p&gt;
  &lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
  &lt;td&gt;
  &lt;p align="center"&gt;1&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p&gt;Reads row ID=1, gets the balance = 100
  and holds shared lock on the row.&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p&gt;&amp;nbsp;&lt;/p&gt;
  &lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
  &lt;td&gt;
  &lt;p align="center"&gt;2&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p&gt;&amp;nbsp;&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p&gt;Updates row id=2 with balance = balance -
  10 and holds the lock&lt;/p&gt;
  &lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
  &lt;td&gt;
  &lt;p align="center"&gt;3&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p&gt;Tries to read the row ID = 2 but it's
  already exclusively locked by the writer so the statement is waiting for the
  lock to be released.&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p&gt;&amp;nbsp;&lt;/p&gt;
  &lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
  &lt;td&gt;
  &lt;p align="center"&gt;4&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p&gt;&amp;nbsp;&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p&gt;Tries to update row id=1 but the row is
  locked by the other statement. Transaction deadlocks.&lt;/p&gt;
  &lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
  &lt;td&gt;
  &lt;p align="center"&gt;5&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p&gt;Reader is chosen as the deadlock victim
  as there is less work to "rollback".&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p&gt;&amp;nbsp;&lt;/p&gt;
  &lt;/td&gt;
 &lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Approach
no 2&lt;/b&gt;&lt;/p&gt;

&lt;p&gt;The reason for the deadlock above is the
"progressive" locking of the rows as the SELECT query continues along the
table. &amp;nbsp;The simple solution is to use the
TABLOCK hint in the query to make sure that the entire table is locked in one
"go". &lt;/p&gt;

&lt;p&gt;&lt;a href="http://blogs.conchango.com/photos/conchango_bloggers/picture10051.aspx" target="_blank"&gt;&lt;img src="http://blogs.conchango.com/photos/conchango_bloggers/images/10051/original.aspx" border="0"&gt;&lt;/a&gt;&lt;br&gt;&lt;/p&gt;

&lt;p&gt;This approach works exactly as expected (no
inconsistent results) the downside however is an immediate drop in "writer" performance
as the following table illustrates:&lt;/p&gt;

&lt;table border="1" cellpadding="0" cellspacing="0"&gt;
 &lt;tbody&gt;&lt;tr&gt;
  &lt;td&gt;
  &lt;p&gt;&lt;b&gt;Operation&lt;/b&gt;&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p&gt;&lt;b&gt;Total
  # of executions (average of 3 runs)&lt;/b&gt;&lt;/p&gt;
  &lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
  &lt;td&gt;
  &lt;p&gt;Reads (totals retrieved)&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p&gt;3823&lt;/p&gt;
  &lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
  &lt;td&gt;
  &lt;p&gt;Writes (transfers executed)&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p&gt;3072&lt;/p&gt;
  &lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
  &lt;td&gt;
  &lt;p&gt;Inconsistent results&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p&gt;0&lt;/p&gt;
  &lt;/td&gt;
 &lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;

&lt;p&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Approach
no 3&lt;/b&gt;&lt;/p&gt;

&lt;p&gt;The third approach was something I was very
keen to test: in SQL Server 2005 there is a magic option (actually a couple of
them) which enables row versioning. &lt;/p&gt;

&lt;p&gt;Row versioning is not a new thing (in fact
Oracle RDBMS used it "by definition" for as far as I care to remember) and the
whole concept works (very roughly) as follows:&lt;/p&gt;

&lt;table border="1" cellpadding="0" cellspacing="0"&gt;
 &lt;tbody&gt;&lt;tr&gt;
  &lt;td&gt;
  &lt;p align="center"&gt;&lt;b&gt;Time&lt;/b&gt;&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p&gt;&lt;b&gt;Reader&lt;/b&gt;&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p&gt;&lt;b&gt;Writer&lt;/b&gt;&lt;/p&gt;
  &lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
  &lt;td&gt;
  &lt;p align="center"&gt;1&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p&gt;&amp;nbsp;&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p&gt;Writer transaction starts &lt;/p&gt;
  &lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
  &lt;td&gt;
  &lt;p align="center"&gt;2&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p&gt;Reader transaction starts&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p&gt;&amp;nbsp;&lt;/p&gt;
  &lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
  &lt;td&gt;
  &lt;p align="center"&gt;3&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p&gt;&amp;nbsp;&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p&gt;Row ID=1 Updated with balance+10=110, new
  version&amp;nbsp; number applied to the row and the
  old version of the row gets stored in the "version store"&lt;/p&gt;
  &lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
  &lt;td&gt;
  &lt;p align="center"&gt;4&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p&gt;Tries to retrieve row ID=1, the engine
  realizes that the row version is different than it was at T=2 so retrieves
  previous version of the row from the "version store" with balance = 100.&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p&gt;&amp;nbsp;&lt;/p&gt;
  &lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
  &lt;td&gt;
  &lt;p align="center"&gt;5&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p&gt;&amp;nbsp;&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p&gt;Row ID=2 Updated with balance-10=90, new
  version&amp;nbsp; "number" applied and the old
  version of the row gets stored in the "version store"&lt;/p&gt;
  &lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
  &lt;td&gt;
  &lt;p align="center"&gt;6&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p&gt;Tires to retrieve row ID = 2, the engine
  realizes again that the row has been updated after the statement started so
  retrieves previous version from the "version store" with balance = 100.&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p&gt;&amp;nbsp;&lt;/p&gt;
  &lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
  &lt;td&gt;
  &lt;p align="center"&gt;7&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p&gt;Produces correct total of 200 exactly as
  it was at the time the statement started.&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p&gt;&amp;nbsp;&lt;/p&gt;
  &lt;/td&gt;
 &lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;The simplest approach to enable statement
level row versioning is to use READ_COMMITTED_SNAPSHOT database option. &lt;/p&gt;

&lt;p&gt;&lt;a href="http://blogs.conchango.com/photos/conchango_bloggers/picture10048.aspx" target="_blank"&gt;&lt;img src="http://blogs.conchango.com/photos/conchango_bloggers/images/10048/original.aspx" border="0"&gt;&lt;/a&gt;&lt;br&gt;&lt;/p&gt;

&lt;p&gt;Executing the above statement enables row
versioning for statements (not transactions) executing at READ_COMMITED
isolation level which incidentally is the default isolation level. This means
that no changes in the application are usually necessary to benefit from this
type of row versioning. After setting the option the test app produced
following results:&amp;nbsp; &lt;/p&gt;

&lt;table border="1" cellpadding="0" cellspacing="0"&gt;
 &lt;tbody&gt;&lt;tr&gt;
  &lt;td&gt;
  &lt;p&gt;&lt;b&gt;Operation&lt;/b&gt;&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p&gt;&lt;b&gt;Total
  # of executions (average of 3 runs)&lt;/b&gt;&lt;/p&gt;
  &lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
  &lt;td&gt;
  &lt;p&gt;Reads (totals retrieved)&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p&gt;3103&lt;/p&gt;
  &lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
  &lt;td&gt;
  &lt;p&gt;Writes (transfers executed)&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p&gt;24855&lt;/p&gt;
  &lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
  &lt;td&gt;
  &lt;p&gt;Inconsistent results&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p&gt;0&lt;/p&gt;
  &lt;/td&gt;
 &lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;As you can see this approach not only
solves the problem of inconsistent result but also substantially increases
throughput of the app. This is because one of the interesting side effects of row
versioning is the fact that no locks are applied during execution of SELECT
statements. When READ_COMMITTED_SNAPSHOT is active only "writers" block "writers"
which is in stark contrast to default SQL Server behaviour where everybody
locks just about everybody else (only readers do not block readers).&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Approach
no 4&lt;/b&gt;&lt;/p&gt;

&lt;p&gt;READ_COMMITTED_SNAPSHOT works at the
statement level i.e. consistent view of the data is maintained relative to the
start of the statement. In case of our test application this is perfectly
sufficient because GetTotal () stored procedure executes a single select
statement.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;If we
wanted to maintain consistency at transaction level, the
ALLOW_SNAPSHOT_ISOLATION option has to be set to ON. The downside is that in
such a case row versioning has to be explicitly requested by setting the transaction
isolation level to SNAPSHOT.&lt;/p&gt;

&lt;p&gt;&lt;a href="http://blogs.conchango.com/photos/conchango_bloggers/picture10044.aspx" target="_blank"&gt;&lt;img src="http://blogs.conchango.com/photos/conchango_bloggers/images/10044/original.aspx" border="0"&gt;&lt;/a&gt;&lt;br&gt;&lt;/p&gt;

&lt;p&gt;Once the reader procedure has been modified
the test app produced following results:&lt;/p&gt;

&lt;table border="1" cellpadding="0" cellspacing="0"&gt;
 &lt;tbody&gt;&lt;tr&gt;
  &lt;td&gt;
  &lt;p&gt;&lt;b&gt;Operation&lt;/b&gt;&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p&gt;&lt;b&gt;Results
  (average)&lt;/b&gt;&lt;/p&gt;
  &lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
  &lt;td&gt;
  &lt;p&gt;Reads (totals retrieved)&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p&gt;3156&lt;/p&gt;
  &lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
  &lt;td&gt;
  &lt;p&gt;Writer (transfers executed)&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p&gt;22697&lt;/p&gt;
  &lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
  &lt;td&gt;
  &lt;p&gt;Inconsistent results&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p&gt;0&lt;/p&gt;
  &lt;/td&gt;
 &lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;The differences in performance of both
approaches using row versioning should be considered negligible as the
performance varied quite widely between runs. &lt;/p&gt;

&lt;p&gt;&lt;b&gt;Wrap
Up&lt;/b&gt;&lt;/p&gt;

&lt;p&gt;As these results hopefully illustrate row
versioning not only solves some annoying result "consistency" problems but also
substantially increases performance of our sample app (see the following
graph).&lt;/p&gt;

&lt;p&gt;&lt;a href="http://blogs.conchango.com/photos/conchango_bloggers/picture10045.aspx" target="_blank"&gt;&lt;img src="http://blogs.conchango.com/photos/conchango_bloggers/images/10045/original.aspx" border="0"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I would not recommend blindly applying row
versioning strategies in every case as they put additional stress o the TEMPDB
(all updates have to save old row versions in there) and have some other
surprising properties but it's clearly an option worth considering for
scenarios where concurrency (locking and/or deadlocks) becomes an issue. Following
two graphs illustrate number of lock requests with row versioning disabled and
enabled. As you can clearly see the differences are substantial and so will be
the scalability of a system with frequent and concurrent reads and updates.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Lock requests and waits with READ_COMMITTED isolation level.&lt;/b&gt;&lt;br&gt; &lt;/p&gt;

&lt;p&gt;&lt;a href="http://blogs.conchango.com/photos/conchango_bloggers/picture10046.aspx" target="_blank"&gt;&lt;/a&gt;&lt;a href="http://blogs.conchango.com/photos/conchango_bloggers/picture10046.aspx" target="_blank"&gt;&lt;img src="http://blogs.conchango.com/photos/conchango_bloggers/images/10046/500x345.aspx" border="0"&gt;&lt;/a&gt;&lt;br&gt;&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Lock requests and waits with READ_COMMITTED_SNAPSHOT isolation level (read line
illustrates processor usage during the test as the number of lock requests and is
minimal)&lt;/b&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="http://blogs.conchango.com/photos/conchango_bloggers/picture10047.aspx" target="_blank"&gt;&lt;img src="http://blogs.conchango.com/photos/conchango_bloggers/images/10047/500x345.aspx" border="0"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;img src="http://blogs.conchango.com/aggbug.aspx?PostID=10052" width="1" height="1"&gt;</description><enclosure url="http://blogs.conchango.com/marcinkaluza/attachment/10052.ashx" length="23819" type="application/x-zip-compressed" /><category domain="http://blogs.conchango.com/marcinkaluza/archive/tags/.NET/default.aspx">.NET</category><category domain="http://blogs.conchango.com/marcinkaluza/archive/tags/SQL+Server/default.aspx">SQL Server</category></item><item><title>Katmai: Times They Are A-Changin'</title><link>http://blogs.conchango.com/marcinkaluza/archive/2007/11/20/Katmai_3A00_-Times-They-Are-A_2D00_Changin_2700_.aspx</link><pubDate>Tue, 20 Nov 2007 15:49:00 GMT</pubDate><guid isPermaLink="false">e847c0e7-38d9-45c0-b593-56747303e088:9123</guid><dc:creator>marcin.kaluza</dc:creator><slash:comments>0</slash:comments><comments>http://blogs.conchango.com/marcinkaluza/comments/9123.aspx</comments><wfw:commentRss>http://blogs.conchango.com/marcinkaluza/commentrss.aspx?PostID=9123</wfw:commentRss><description>&lt;p&gt;SQL Server 2008 is upon us and if there is one feature about which I am somewhat excited (sad geek, I know) is the plethora of &lt;a href="http://technet.microsoft.com/en-us/library/ms186724(SQL.100).aspx" target="_blank"&gt;new data types&lt;/a&gt; designed purely to store dates and times. To explain the reasons for this excitement let me just list the problems with existing SQL types.&lt;br /&gt;
&lt;br /&gt;
The first major issue is that of resolution: &lt;em&gt;Smalldatetime&lt;/em&gt; has resolution of one minute which makes it pretty much useless if you want to store in it anything but the date. The &lt;em&gt;Datetime&lt;/em&gt; on the other hand offers a fixed resolution of 0.00333 sec which seems to be more than enough for most applications. However it is not the fact the resolution is too high or too low, it is the fact that it is fixed which is the problem. Such relatively high resolution is not required by most apps and because it is given by default, whether we like it or not, leads us directly to problem no two.&lt;br /&gt;
&lt;br /&gt;
The second issue is that of size: 8 bytes long &lt;em&gt;Datetime&lt;/em&gt; is an awful lot of space especially when used as a partitioning field of a FACT table in a data warehouse with hundreds of millions of rows. If you think that these days size of the data type is not that important think twice: why do you think Microsoft introduced &lt;em&gt;VARDECIMAL&lt;/em&gt; in SP2 for SQL Server 2005?&lt;br /&gt;
&lt;br /&gt;
The third major issue is that of range: the range of values that can be stored in a &lt;em&gt;Datetime&lt;/em&gt; column does not match the range of .NET&amp;rsquo;s System.DateTime. Because the minimum value for &lt;em&gt;Datetime&lt;/em&gt; supported in SQL 2005 (01/01/1753) is different from System.DateTime.MinValue (01/01/0001) as soon as you try to hand the latter to SQL Server, the program blows up nicely.&lt;br /&gt;
&lt;br /&gt;
The fourth problem is related to storage of both date and time in the same column. What if someone wants to see all the transactions that took place after 06PM? The only way to achieve this is to use the DATEPART function to extract the relevant portion of the field and this immediately rules out any index usage (lets not get into calculated columns and function based indexes at this point). The end result is that although the query may return only a fraction of all the rows we&amp;rsquo;re still looking at a full table scan. Now let&amp;rsquo;s look at the problem of filtering by date: if we want to extract all the rows where date is equal to 13/11/2007 what we really say is 13/11/2007 00:00:00. This means that querying for events that took place on a particular day becomes awkward exercise along the lines of Transaction_Time between &amp;lsquo;13/11/2007 00:00:00&amp;rsquo; and &amp;lsquo;13/11/2007 23:59:59&amp;rsquo;. The common approach to solving this problem is to use two separate fields to store date and time, this however leads to increased storage requirements.&lt;br /&gt;
&lt;br /&gt;
SQL Server 2008 finally offers an elegant answer to all of these problems: there are separate &lt;em&gt;Date&lt;/em&gt; and &lt;em&gt;Time&lt;/em&gt; data types and to make things even better the &lt;em&gt;Time&lt;/em&gt; has varying &amp;quot;precision&amp;quot;. Both of them are tiny (3 bytes for the &lt;em&gt;Date&lt;/em&gt; and 3-5 bytes for the &lt;em&gt;Time&lt;/em&gt; depending on precision) so we can store date and time separately in just 6 bytes (assuming time resolution of 1 sec).&lt;br /&gt;
&lt;br /&gt;
To make things even better there is now a &amp;ldquo;proper&amp;rdquo; &lt;em&gt;Datetime2&lt;/em&gt; data type which is perfectly &amp;ldquo;aligned&amp;rdquo; with .NET&amp;rsquo;s System.DateTime. This alignment is important for the reasons I&amp;#39;ve already mentioned: loss of precision and problems when trying to query/insert/update data using DateTime.MinValue are two major examples.&lt;br /&gt;
&lt;br /&gt;
And last but not least the there is a &lt;em&gt;Datetimeoffset&lt;/em&gt; data type which is identical in range and resolution to &lt;em&gt;Datetime2&lt;/em&gt; but stores the time zone information as well. This comes very handy when data stored in central database is accessed from different locations (countries). In such a case users across various time zones will be able to see the values stored in the database converted to their local times: 12:00 in London is 13:00 in Warsaw etc. &lt;br /&gt;
&lt;br /&gt;
I&amp;#39;d love to say that people from SQL Server team did an excellent job as they truly went an extra mile to solve all of the date and time problems, however as soon as I put all those new data types to test I discovered some teething issues. New data types work as expected with ADO .NET when using Dataset or IDataReader: the values returned are sensibly mapped to .NET types (DateTime, TimeSpan or DateTimeOffset). My biggest issue however is that some of them do not work with LINQ to SQL, or at least I was not able to make them work. When mapping &lt;em&gt;Time&lt;/em&gt; to either TimeSpan or DateTime, InvalidCastException gets thrown. Things get even more interesting when mapping &lt;em&gt;Datetimeoffset&lt;/em&gt; column to a field of equivalent .NET type: all you get is System.Security.VerificationException with a mesage that says: &amp;quot;Operation could destabilize the runtime&amp;quot;.&amp;nbsp;&lt;br /&gt; &lt;/p&gt;&lt;p&gt;The only way (with which I was able to come up so far) to overcome the Time mapping issue is to pass varchars/strings instead of &lt;em&gt;Time&lt;/em&gt; as arguments to stored procedures and merge &lt;em&gt;Times&lt;/em&gt; with &lt;em&gt;Dates&lt;/em&gt; on the way back to produce a &lt;em&gt;Datetime&lt;/em&gt;. In spite of all these issues in an &amp;quot;airline project&amp;quot; on which I&amp;#39;m working right now, finally having separate data types and columns for dates and times is a godsend.&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;img src="http://blogs.conchango.com/aggbug.aspx?PostID=9123" width="1" height="1"&gt;</description><category domain="http://blogs.conchango.com/marcinkaluza/archive/tags/.NET/default.aspx">.NET</category><category domain="http://blogs.conchango.com/marcinkaluza/archive/tags/Katmai/default.aspx">Katmai</category><category domain="http://blogs.conchango.com/marcinkaluza/archive/tags/LINQ/default.aspx">LINQ</category><category domain="http://blogs.conchango.com/marcinkaluza/archive/tags/SQL+Server/default.aspx">SQL Server</category></item></channel></rss>