Optimistic Locking with Snapshot Isolation SQL Server 2008’s Snapshot Isolation mode provides another mechanism for implement-ing optimistic lockimplement-ing through its automatic row v
Trang 1CHAPTER 37 Locking and Performance
equivalent to the Serializable Read isolation level The following hypothetical
example demonstrates the usage of the HOLDLOCKstatement within a transaction:
declare @seqno int
begin transaction
get a UNIQUE sequence number from sequence table
SELECT @seqno = isnull(seq#,0) + 1
from sequence WITH (HOLDLOCK)
in the absence of HOLDLOCK, shared lock will be released
and if some other concurrent transaction ran the same
command, both of them could get the same sequence number
UPDATE sequence
set seq# = @seqno
now go do something else with this unique sequence number
commit tran
NOTE
As discussed earlier in this chapter, in the “Deadlocks” section, using HOLDLOCKin this
manner leads to potential deadlocks between processes executing the transaction at
the same time For this reason, the HOLDLOCKhint, as well as the REPEATABLEREAD
andSERIALIZABLEhints, should be used sparingly, if at all In this example, it might be
better for the SELECTstatement to use an update or an exclusive lock on the
sequencetable, using the hints discussed later in this chapter, in the section “Lock
Type Hints.” Another option would be to use an application lock, as discussed
previous-ly in this chapter, in the section “Using Application Locks.”
NOLOCK—You can use this option to specify that no shared lock be placed on the
resource This option is similar to running a query at Isolation Level 0 (Read
Uncommitted), which allows the query to ignore exclusive locks and read
uncom-mitted changes The NOLOCKoption is a useful feature in reporting environments,
where the accuracy of the results is not critical
READUNCOMMITTED—This is the same as specifying the Read Uncommitted mode
when using the SET TRANSACTION ISOLATION LEVELcommand, and it is the same as
theNOLOCKtable hint
READCOMMITTED—This is the same as specifying the Read Committed mode when
you use the SET TRANSACTION ISOLATION LEVELcommand The query waits for
exclusive locks to be released before reading the data This is the default locking
isolation mode for SQL Server If the database option READ_COMMITTED_SNAPSHOTis
ON, SQL Server does not acquire shared locks on the data and uses row versioning
Trang 2READCOMMITTEDLOCK—This option specifies that read operations acquire shared
locks as data is read and release those locks when the read operation is completed,
regardless of the setting of the READ_COMMITTED_SNAPSHOTdatabase option
REPEATABLEREAD—This is the same as specifying Repeatable Read mode with the
SET TRANSACTION ISOLATION LEVELcommand It prevents nonrepeatable reads
within a transaction and behaves similarly to the HOLDLOCKhint
SERIALIZABLE—This is the same as specifying Serializable Read mode with the SET
TRANSACTION ISOLATION LEVELcommand It prevents phantom reads within a
trans-action and behaves similarly to using the HOLDLOCKhint
READPAST—This hint specifies that the query skip over the rows or pages locked by
other transactions, returning only the data that can be read Read operations
specify-ingREADPASTare not blocked When specified in an UPDATEorDELETEstatement,
READPASTis applied only when reading data to identify which records to update
READPASTcan be specified only in transactions operating at the Read Committed or
Repeatable Read isolation levels This lock hint is useful when reading information
from a SQL Server table used as a work queue A query using READPASTskips past
queue entries locked by other transactions to the next available queue entry, without
having to wait for the other transactions to release their locks
Lock Granularity Hints
You can use to override lock granularity:
ROWLOCK—You can use this option to force the Lock Manager to place a row-level
lock on a resource instead of a page-level or a table-level lock You can use this
option in conjunction with the XLOCKlock type hint to force exclusive row locks
PAGLOCK—You can use this option to force a page-level lock on a resource instead of
a row-level or table-level lock You can use this option in conjunction with the XLOCK
lock type hint to force exclusive page locks
TABLOCK—You can use this option to force a table-level lock instead of a row-level
or page-level lock You can use this option in conjunction with the HOLDLOCKtable
hint to hold the table lock until the end of the transaction
TABLOCKX—You can use this option to force a table-level exclusive lock instead of a
row-level or page-level lock No shared or update locks are granted to other
transac-tions as long as this option is in effect If you are planning maintenance on a SQL
Server table and you don’t want interference from other transactions, using this
option is one of the ways to essentially put a table into a single-user mode
Lock Type Hints
You can use the following optimizer hints to override the lock type that SQL Server uses:
UPDLOCK—This option is similar to HOLDLOCKexcept that whereas HOLDLOCKuses a
shared lock on the resource, UPDLOCKplaces an update lock on the resource for the
Trang 3CHAPTER 37 Locking and Performance
duration of the transaction This allows other processes to read the information, but
not acquire update or exclusive locks on the resource This option provides read
repeatability within the transaction while preventing deadlocks that can result when
usingHOLDLOCK
XLOCK—This option places an exclusive lock on the resource for the duration of the
transaction This prevents other processes from acquiring locks on the resource
Optimistic Locking
With many applications, clients need to fetch the data to browse through it, make
modifi-cations to one or more rows, and then post the changes back to the database in SQL
Server These human-speed operations are slow in comparison to machine-speed
opera-tions, and the time lag between the fetch and post might be significant (Consider a user
who goes to lunch after retrieving the data.)
For these applications, you would not want to use normal locking schemes such as
SERIALIZABLEorHOLDLOCKto lock the data so it can’t be changed from the time the user
retrieves it to the time he or she applies any updates This would violate one of the key
rules for minimizing locking contention and deadlocks that you should not allow user
interaction within transactions You would also lose all control over the duration of the
transaction In a multiuser OLTP environment, the indefinite holding of the shared locks
could significantly affect concurrency and overall application performance due to blocking
on locks and locking contention
On the other hand, if the locks are not held on the rows being read, another process could
update a row between the time it was initially read and when the update is posted When
the first process applies the update, it would overwrite the changes made by the other
process, resulting in a lost update
So how do you implement such an application? How do you allow users to retrieve
infor-mation without holding locks on the data and still ensure that lost updates do not occur?
Optimistic locking is a technique used in situations in which reading and modifying data
processes are widely separated in time Optimistic locking helps a client avoid overwriting
another client’s changes to a row without holding locks in the database
One approach for implementing optimistic locking is to use the rowversiondata type
Another approach is to take advantage of the optimistic concurrency features of snapshot
isolation
Optimistic Locking Using the rowversion Data Type
SQL Server 2008 provides a special data type called rowversionthat can be used for
opti-mistic locking purposes within applications The purpose of the rowversiondata type is to
serve as a version number in optimistic locking schemes SQL Server automatically
gener-ates the value for a rowversioncolumn whenever a row that contains a column of this
type is inserted or updated The rowversiondata type is an 8-byte binary data type, and
Trang 4other than guaranteeing that the value is unique and monotonically increasing, the value
is not meaningful; you cannot look at the individual bytes and make any sense of them
NOTE
In previous versions of SQL Server, the rowversiondata type was also referred to as
thetimestampdata type While this data type synonym still exists in SQL Server 2008,
it has been deprecated and the rowversiondata type name should be used instead to
ensure future compatibility
In an application that uses optimistic locking, the client reads one or more records from
the table, being sure to retrieve the primary key and current value of therowversion
column for each row, along with any other desired data columns Because the query is not
run within a transaction, any locks acquired for theSELECTare released after the data has
been read At some later time, when the client wants to update a row, it must ensure that
no other client has changed the same row in the intervening time TheUPDATEstatement
must include aWHEREclause that compares therowversionvalue retrieved with the
origi-nal query, with the currentrowversionvalue for the record in the database If the
rowversionvalues match—that is, if the value that was read is the same as the value
currently in the database—no changes to that row have occurred since it was originally
retrieved Therefore, the change attempted by the application can proceed If the
rowversionvalue in the client application does not match the value in the database, that
particular row has been changed since the original retrieval of the record As a result, the
state of the row that the application is attempting to modify is not the same as the row
that currently exists in the database As a result, the transaction should not be allowed to
take place, to avoid the lost update problem
To ensure that the client application does not overwrite the changes made by another
process, the client needs to prepare the T-SQLUPDATEstatement in a special way, using
therowversioncolumn as a versioning marker The following pseudocode represents the
general structure of such an update:
UPDATE theTable
SET theChangedColumns = theirNewValues
WHERE primaryKeyColumns = theirOldValues
AND rowversion = itsOldValue
Because theWHEREclause includes the primary key, theUPDATEcan apply only to exactly
one row or to no rows; it cannot apply to more than one row because the primary key is
unique The second part of theWHEREclause provides the optimistic “locking.” If another
client has updated the row, therowversionno longer has its old value (remember that the
server changes therowversionvalue automatically with each update), and theWHEREclause
does not match any rows The client needs to check whether any rows were updated If the
number of rows affected by the update statement is zero, the row has been modified since
Trang 5CHAPTER 37 Locking and Performance
it was originally retrieved The application can then choose to reread the data or do
what-ever recovery it deems appropriate This approach has one problem: how does the
applica-tion know whether it didn’t match the row because therowversionwas changed, because
the primary key had changed, or because the row had been deleted altogether?
In SQL Server 2000, there was an undocumented tsequal()function (which was
docu-mented in prior releases) that could be used in a WHEREclause to compare the rowversion
value retrieved by the client application with the rowversionvalue in the database If the
rowversionvalues matched, the update would proceed If not, the update would fail, with
error message 532, to indicate that the row had been modified Unfortunately, this
func-tion is no longer provided in SQL Server 2005 and later releases Any attempt to use it
now results in a syntax error As an alternative, you can programmatically check whether
the update modified any rows, and if not, you can check whether the row still exists and
return the appropriate message Listing 37.7 provides an example of a stored procedure
that implements this strategy
LISTING 37.7 An Example of a Procedure for Optimistic Locking
create proc optimistic_update
@id int, provide the primary key for the record
@data_field_1 varchar(10), provide the data value to be updated
@rowversion rowversion pass in the rowversion value retrieved with
the initial data retrieval as
Attempt to modify the record
update data_table
set data_field_1 = @data_field_1
where id = @id
and versioncol = @rowversion
Check to see if no rows updated
IF @@ROWCOUNT=0
BEGIN
if exists (SELECT * FROM data_table WHERE id=@id)
The row exists but the rowversions don’t match
begin
raiserror (‘The row with id “%d” has been updated since it was read’,
10, 1, @id) return -101
end
else the row has been deleted
begin
raiserror (‘The row with id “%d” has been deleted since it was read’,
10, 2, @id) return -102
end
end
Trang 6ELSE
PRINT ‘Data Updated’
return 0
Using this approach, if the update doesn’t modify any rows, the application receives an
error message and knows for sure that the reason the update didn’t take place is that
either the rowversionvalue didn’t match or the row was deleted If the row is found and
therowversionvalues match, the update proceeds normally
Optimistic Locking with Snapshot Isolation
SQL Server 2008’s Snapshot Isolation mode provides another mechanism for
implement-ing optimistic lockimplement-ing through its automatic row versionimplement-ing If a process reads data within
a transaction when Snapshot Isolation mode is enabled, no locks are acquired or held on
the current version of the data row The process reads the version of the data at the time
of the query Because no locks are held, it doesn’t lead to blocking, and another process
can modify the data after it has been read If another process does modify a data row read
by the first process, a new version of the row is generated If the original process then
attempts to update that data row, SQL Server automatically prevents the lost update
problem by checking the row version In this case, because the row version is different,
SQL Server prevents the original process from modifying the data row When it attempts
to modify the data row, the following error message appears:
Msg 3960, Level 16, State 4, Line 2
Snapshot isolation transaction aborted due to update conflict You cannot use
snapshot isolation to access table ‘dbo.data_table’ directly or indirectly in
database ‘bigpubs2008’ to update, delete, or insert the row that has been modified
or deleted by another transaction Retry the transaction or change the isolation
level for the update/delete statement
To see how this works, you can create the following table:
use bigpubs2008
go
The first statement is used to disable any previously created
DDL triggers in the database which would prevent creating a new table
DISABLE TRIGGER ALL ON DATABASE
go
create table data_table
(id int identity,
data_field_1 varchar(10),
timestamp timestamp)
go
insert data_table (data_field_1) values (‘foo’)
go
Next, you need to ensure that bigpubs2008is configured to allow snapshot isolation:
Trang 7CHAPTER 37 Locking and Performance
ALTER DATABASE bigpubs2008 SET ALLOW_SNAPSHOT_ISOLATION ON
In one user session, you execute the following SQL statements:
SET TRANSACTION ISOLATION LEVEL SNAPSHOT
go
begin tran
select * from data_table
go
id data_field_1 timestamp
- -
-1 foo 0x0000000000000BC4
Now, in another user session, you execute the following UPDATEstatement:
update data_table set data_field_1 = ‘bar’
where id = 1
Then you go back to the original session and attempt the following update:
update data_table set data_field_1 = ‘fubar’
where id = 1
go
Msg 3960, Level 16, State 4, Line 2
Snapshot isolation transaction aborted due to update conflict You cannot use
snapshot isolation to access table ‘dbo.data_table’ directly or indirectly in
database ‘bigpubs2008’ to update, delete, or insert the row that has been modified
or deleted by another transaction Retry the transaction or change the isolation
level for the update/delete statement
Note that for the first process to hold on to the row version, the SELECTandUPDATE
state-ments must be run in the same transaction When the transaction is committed or rolled
back, the row version acquired by the SELECTstatement is released However, because the
SELECTstatement run at the Snapshot Isolation level does not hold any locks, there are no
locks being acquired or held by that SELECTstatement within the transaction, so it avoids
the problems that would normally be encountered by using HOLDLOCKor the Serializable
Read isolation level Because no locks were held on the data row, the other process was
allowed to update the row after it was retrieved, generating a new version of the row The
automatic row versioning provided by SQL Server’s Snapshot Isolation mode prevented
the first process from overwriting the update performed by the second process, thereby
preventing a lost update
Trang 8CAUTION
Locking contention is prevented in the preceding example only because the transaction
performed only a SELECTbefore attempting the UPDATE A SELECTrun with Snapshot
Isolation mode enabled reads the current version of the row and does not acquire or
hold locks on the actual data row However, if the process were to perform any other
modification on the data row, the update or exclusive locks acquired would be held
until the end of the transaction, which could lead to locking contention, especially if
user interaction is allowed within the transaction after the update or exclusive locks are
acquired
Also, be aware of the overhead generated in tempdbwhen Snapshot Isolation mode is
enabled for a database, as described in the section “Transaction Isolation Levels in
SQL Server,” earlier in this chapter
Because of the overhead incurred by snapshot isolation and the cost of having to roll
back update conflicts, you should consider using Snapshot Isolation mode only to
pro-vide optimistic locking for systems where there is little concurrent updating of the
same resource so that it is unlikely that your transactions have to be rolled back
because of an update conflict
Summary
Locking is critical in a multiuser environment for providing transaction isolation SQL
Server supports all ANSI-defined transaction isolation levels, including the Snapshot
Isolation level for applications that can benefit from optimistic concurrency The Lock
Manager in SQL Server automatically locks data at the row level or higher, as necessary, to
provide the appropriate isolation while balancing the locking overhead with concurrent
access to the data It is important to understand how locking works and what its effect is
on application performance to develop efficient queries and applications
SQL Server provides a number of tools for monitoring and identifying locking problems
and behavior In addition, SQL Server provides a number of table-locking hints that give the
developer better control over the default lock types and granularity used for certain queries
Although following the guidelines to minimize locking contention in applications is
important, another factor that affects locking behavior and query performance is the
actual database design Chapter 38, “Database Design and Performance,” discusses
data-base design and its effect on datadata-base performance and provides guidelines to help ensure
that transactions and T-SQL code run efficiently
Trang 9This page intentionally left blank
Trang 10Database Design and
Performance
What’s New in Database Design and Performance
Basic Tenets of Designing for Performance
Logical Database Design Issues
Denormalizing a Database
Database Filegroups and Performance
RAID Technology
SQL Server and SAN Technology
Various factors contribute to the optimal performance of
a database application Some of these factors include logical
database design (rules of normalization), physical database
design (denormalization, indexes, data placement), choice
of hardware (SMP servers/multiprocessor servers), network
bandwidth (LAN versus WAN), client and server
configura-tion (memory, CPU), data access techniques (ODBC, ADO,
OLEDB), and application architecture (two-tier versus
n-tier) This chapter helps you understand some of the key
database design issues to ensure that you have a reliable
high-performance application
NOTE
Index design is often considered part of physical
data-base design Because index design guidelines and the
impact of indexes on query and update performance
are covered in detail in Chapter 34, “Data Structures,
Indexes and Performance,” this chapter does not
dis-cuss index design It focuses instead on other aspects
of database design and performance
What’s New in Database Design
and Performance
Many of the database design and performance
considera-tions that applied to previous versions of SQL Server still
apply to SQL Server 2008 These principles are basic in