1. Transactions and Locking
SQL
Server issues and holds on to locks for the duration of a transaction
to ensure the isolation and consistency of the modifications. Data
modifications that occur within a transaction acquire exclusive locks,
which are then held until the completion of the transaction. Shared
locks, or read locks, are held for only as long as the statement needs
them; usually, a shared lock is released as soon as data has been read
from the resource (for example, row, page, table). You can modify the
length of time a shared lock is held by using keywords such as HOLDLOCK in a query or setting the REPEATABLE_READ or SERIALIZABLE lock isolation levels. If one of these options is specified, shared locks are held until the completion of the transaction.
What this means for you as a database application
developer is that you should try to hold on to as few locks or as small a
lock as possible for as short a time as possible to avoid locking
contention between applications and to improve concurrency and
application performance. The simple rule when working with transactions
is to keep them short and keep them simple. In other words, you should
do what you need to do in the most concise manner, in the shortest
possible time. You should keep any extraneous commands that do not need
to be part of the logical unit of work—such as SELECT statements, commands for dropping temporary tables, commands for setting up local variables, and so on—outside the transaction.
To modify the manner in which a transaction and its locks can be handled by a SELECT statement, you can issue the SET TRANSACTION ISOLATION LEVEL
statement. This statement allows the query to choose how much it is
protected against other transactions modifying the data being used. The SET TRANSACTION ISOLATION LEVEL statement has the following mutually exclusive options:
READ COMMITTED—
This setting is the default for SQL Server. Modifications made within a
transaction are locked exclusively, and the changes cannot be viewed by
other user processes until the transaction completes. Commands that
read data only hold shared locks on the data for as long as they are
reading it. Because other transactions are not blocked from modifying
the data after you have read it within your transaction, subsequent
reads of the data within the transaction might encounter nonrepeatable reads or phantom data.
READ UNCOMMITTED—
With this level of isolation, one transaction can read the
modifications made by other transactions prior to being committed. This
is, therefore, the least restrictive isolation level, but it is one that
allows the reading of dirty and uncommitted data. This option has the
same effect as issuing NOLOCK within SELECT
statements, but it has to be set only once for your connection. This
option should never be used in an application in which accuracy of the
query results is required.
REPEATABLE READ—
When this option is set, as data is read, locks are placed and held on
the data for the duration of the transaction. These locks prevent other
transactions from modifying the data you have read so that you can carry
out multiple passes across the same information and get the same
results each time. This isolation level is obviously more restrictive than READ COMMITTED and READ UNCOMMITTED,
and it can block other transactions. However, although it prevents
nonrepeatable reads, it does not prevent the addition of new rows or phantom rows because only existing data is locked.
SERIALIZABLE—
This option is the most restrictive isolation level because it places a
range lock on the data. This prevents any modifications to the data
being read from until the end of the transaction. It also avoids phantom
reads by preventing rows from being added or removed from the data
range set.
SNAPSHOT—
Snapshot isolation specifies that data read by any statement will see
only data modifications that were committed before the start of the
transaction. The effect is as if the statements in a transaction see a
snapshot of the committed data as it existed at the start of the
transaction. The ALLOW_SNAPSHOT_ISOLATION database option must be set to ON for a transaction to specify the SNAPSHOT isolation level.
READ_COMMITTED_SNAPSHOT Isolation
In addition to the SNAPSHOT isolation level, SQL Server also supports a special form of read-committed isolation, referred to as READ_COMMITTED_SNAPSHOT.
This form of isolation is similar to snapshot isolation, but unlike
snapshot isolation, which sees the version of the data at the start of
the transaction, read committed snapshot queries see the version of the
data at the start of the statement.
To enable the READ_COMMITTED_SNAPSHOT isolation level for queries, you need to enable the READ_COMMITTED_SNAPSHOT database option. Any queries that normally would run at the standard READ_COMMITTED isolation level automatically run at the READ_COMMITTED_SNAPSHOT isolation level, without requiring any code changes.
2. Coding Effective Transactions
Poorly written or inefficient transactions can have a
detrimental effect on concurrency of access to data and overall
application performance. SQL Server can hold locks on a number of
resources while the transaction is open; modified rows acquire exclusive
locks, and other locks might also be held, depending on the isolation
level used. To reduce locking contention for resources, transactions
should be kept as short and efficient as possible. During development,
you might not even notice that a problem exists; the problem might
become noticeable only after the system load is increased and multiple
users are executing transactions simultaneously. Following are some
guidelines to consider when coding transactions to minimize locking
contention and improve application performance:
- Do not return
result sets within a transaction. Doing so prolongs the transaction
unnecessarily. Perform all data retrieval and analysis outside the
transaction.
- Never
prompt for user input during a transaction. If you do, you lose all
control over the duration of the transaction. (Even the best programmers
miss this one on occasion.) On the failure of a transaction, be sure to
issue the rollback before putting up a message box telling the user
that a problem occurred.
- Keep the start and end of a transaction together in the same batch or, better yet, use a stored procedure for the operation.
- Keep
the transaction short. Start the transaction at the point where you
need to do the modifications. Do any preliminary work beforehand.
- Make careful use of different locking schemes and transaction isolation levels.
- If
user input is unavoidable between data retrieval and modification and
you need to handle the possibility of another user modifying the data
values read, use optimistic locking strategies or snapshot isolation
rather than acquiring and holding locks by using HOLDLOCK or other locking options.
- Collect
multiple transactions into one transaction, or batch transactions
together, if appropriate. This advice might seem to go against some of
the other suggestions, but it reduces the amount of overhead SQL Server
will encounter to start, finish, and log the transactions.