Click here to Skip to main content
Email Password   helpLost your password?

Introduction

This article briefly describes how to update the operations block by reading the data being modified. This behaviour can be altered using row versioning. The advantage of using row versioning is greater concurrency, but there are also side effects which are described in this article.

In many cases, I've noticed that applications may use a Read uncommitted isolation level to overcome locking related problems (typically, the amount of time used for an operation). Row versioning significantly reduces the need to use the Read uncommitted isolation level. When the Read committed isolation level can be used without spending too much time on the operation, the correctness of the results is significantly higher.

This behaviour is similar to what is in Oracle databases, although the implementation is somewhat different.

Normal Behaviour

First, we need a simple database for testing, and a table with a row. In the simplest form, this can be created in an existing SQL Server instance using the following command:

-- Create the database
CREATE DATABASE RVTest;

-- Change the context for the connection
USE RVTest;

-- Create a table for testing
CREATE TABLE Test ( 
Column1 varchar(50)
);

-- Insert a row to the table
INSERT INTO Test (Column1) VALUES ('For testing');

Now, when an application starts modifying the data, when done properly, modifications are wrapped inside a transaction. To simulate this, we modify the test row and leave the transaction pending.

BEGIN TRANSACTION;

UPDATE Test 
SET Column1 = 'Modified value';

If the situation is observed from the database side using sp_lock, we can see that an exclusive lock is acquired on the row being modified. Also, a few intent locks are needed at a higher level, but the main concern is in the row lock. The output would look like this:

spid    dbid    ObjId        IndId    Type    Resource    Mode    Status
----    ----    -----        -----    ----    --------    ----    ------
52      8       0            0        DB                  S       GRANT
52      8       2105058535   0        TAB                 IX      GRANT
52      8       2105058535   0        PAG     1:78        IX      GRANT

52      8       2105058535   0        RID     1:78:0      X       GRANT

So far, no problems. Now, when another connection tries to read the same data, it actually tries to get a shared lock on the row. Because an exclusive lock is already granted to the row, a shared lock cannot be given. In this situation, the connection requesting the shared lock is placed in a queue to wait for the lock.

SELECT * FROM Test;

When we use sp_lock again to see the situation, the wait state is seen like following:

spid    dbid    ObjId        IndId    Type    Resource    Mode    Status
----    ----    -----        -----    ----    --------    ----    ------
51      8       2105058535   0        RID     1:78:0      S       WAIT

This situation remains until the transaction from the first session is committed or rolled back. When the transaction ends, exclusive locks are removed, and after that, shared locks can again be granted, and the operation for the second session can continue. In a busy system, the situation in the queues can be like in a gift shop on the day before Christmas.

Using Row Versioning

Row versioning is a database setting which can be modified using the ALTER DATABASE command. To enable row versioning, set READ_COMMITTED_SNAPSHOT to on:

ALTER DATABASE RVTest SET READ_COMMITTED_SNAPSHOT ON;

When executing the command, the database can be in multi-user mode, but there must be no other connections in the database concurrently. If there is, the command doesn't return until all other connections are closed.

Now, when the behaviour is changed, let's have a look at the previous situation. First, one session modifies the test table again:

BEGIN TRANSACTION;

UPDATE Test 
SET Column1 = 'Another modified value';

At this point, nothing is changed. The session has the same locks as previously, but when another session executes a SELECT on the same data, we can see the difference:

SELECT * FROM Test;

Now, the query returns immediately, giving the following result:

Column1
-------
For testing

Most importantly, we see that locks are honored. We don't see the uncommitted data as would happen with the Read uncommitted isolation level. Instead, the result we see is the last committed state of the row.

So, what actually happened? When the modification was done (UPDATE statement), SQL Server took a copy of the data before the data was changed. This copy was placed in tempdb. When the row was queried, the database engine noticed that the row has uncommitted modifications, and based on transaction sequence numbers, it read the original data from tempdb.

Trade-offs

Since there are no free meals, this behaviour has some trade-offs. The most significant of them are:

Conclusion

Row versioning is easy to set up, and it enables much higher concurrency in an environment where the same data is modified and read simultaneously. It uses more resources, and therefore an existing server setup may be inadequate. When used correctly, it will have a very positive impact on the overall throughput of an application.

If the application is designed based on the earlier fact that reading was impossible while an active transaction was modifying the data, row versioning shouldn't be used, or the application must be redesigned where needed.

History

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
GeneralGood Article
Donsw
18:40 25 Jan '09  
Good article. Is this true for what versions of SQL? 2005 and 2008 or all ??
GeneralRe: Good Article
Mika Wendelius
12:31 27 Jan '09  
Thank you!

This was introduced in SQL Server 2005. For some reason I didn't mention this in the article. I'll modify the articles properties. Thanks for noticing.

The need to optimize rises from a bad design.My articles[^]

GeneralRe: Good Article
Donsw
6:39 28 Jan '09  
Glad I can assist. I knew it was added at some point but forgot what version. thanks again.
GeneralInteresting
miies
22:49 12 Oct '08  
Good article. I'm interested in the tradeoff of querying performance vs. the added tempdb I/O. Do you happen to know of any real-life situations where enabling this option will definitely be beneficial?
GeneralRe: Interesting
Mika Wendelius
9:01 13 Oct '08  
Thanks.

The question is a bit hard to answer in brief since there are so many implementation specific factors that affect this.

I've (for some time now) preferred row versioning over the normal locking and in all situations I've encountered this has had a good impact on the system.

I would say that in a system where (system widely) data is mostly modified and simultaneous read access is rare, there's no need to use this, since it's mostly overhead. However, in many system there are 'hot spots' which are constantly updated and queried. These areas may often present the bottleneck of the system.

Also in many databases I've encountered, there may be time consuming reporting done while normal updating occurs. Row versioning helps these kind of cases also.

If possible, try this in a test environment with real test cases (along with stress test). This helps you to estimate the I/O amount and space usage. If you also run the tests with this option on and off, you'll see some guidelines if it's benefitial or not.

Mika

The need to optimize rises from a bad design.

My articles[^]

GeneralRe: Interesting
miies
10:30 13 Oct '08  
Yeah I realized it was a hard question the moment I posted it.. Thanks for trying to answer though. With these hints, I'll go and do some testing/benchmarking in various environments.


Last Updated 17 Oct 2008 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010