1. Lock Compatibility
If a process has already locked a resource, the
granting of lock requests by other transactions on the same resource is
governed by the lock compatibility matrix within SQL Server. Table 1
shows the lock compatibility matrix for the locks most commonly
acquired by the SQL Server Lock Manager, indicating which lock types are
compatible and which lock types are incompatible when requested on the
same resource.
Table 1. SQL Server Lock Compatibility Matrix
Requested Lock Type | Existing Lock Type |
---|
| IS | S | U | IX | SIX | X | Sch-S | SCH-M | BU |
---|
Intent shared | Yes | Yes | Yes | Yes | Yes | No | Yes | No | No |
Shared | Yes | Yes | Yes | No | No | No | Yes | No | No |
Update | Yes | Yes | No | No | No | No | Yes | No | No |
Intent exclusive | Yes | No | No | Yes | No | No | Yes | No | No |
Shared with intent exclusive | Yes | No | No | No | No | No | Yes | No | No |
Exclusive | No | No | No | No | No | No | Yes | No | No |
Schema stability | Yes | Yes | Yes | Yes | Yes | Yes | Yes | No | Yes |
Schema modify | No | No | No | No | No | No | No | No | No |
Bulk update | No | No | No | No | No | No | Yes | No | Yes |
For
example, if a transaction has acquired a shared lock on a resource, the
possible lock types that can be acquired on the resource by other
transactions are intent shared, shared, update, and schema stability
locks. Intent exclusive, SIX, exclusive, schema modification,
and bulk update locks are incompatible with a shared lock and cannot be
acquired on the resource until the shared lock is released.
2. Locking Contention and Deadlocks
In the grand scheme of things, the most likely
culprits of SQL Server application performance problems are typically
poorly written queries, poor database and index design, and locking
contention. Whereas the first two problems result in poor application
performance, regardless of the number of users on the system, locking
contention becomes more of a performance problem as the number of users
increases. It is further compounded by increasingly complex or
long-running transactions.
Locking contention occurs when a transaction requests
a lock type on a resource that is incompatible with an existing lock
type on the resource. By default, the process waits indefinitely for the
lock resource to become available. Locking contention is noticed in the
client application through the apparent lack of response from SQL
Server.
Figure 1
demonstrates an example of locking contention. Process 1 has initiated a
transaction and acquired an exclusive lock on page 1:325. Before
Process 1 can acquire the lock that it needs on page 1:341 to complete
its transaction, Process 2 acquires an exclusive lock on page 1:341.
Until Process 2 commits or rolls back its transaction and releases the
lock on Page 1:341, the lock continues to be held. Because this is not a
deadlock scenario by default, SQL Server takes no action. Process 1 simply waits indefinitely.

2.1 Identifying Locking Contention
When
a client application appears to freeze after submitting a query, this
is often due to locking contention. To identify locking contention
between processes, you can use the SSMS Activity Monitor; use the sp_who2 stored procedure; or query the sys.dm_tran_locks system catalog view. Figure 2 shows an example of a blocking lock as viewed in the SSMS Activity Monitor.
To identify whether a process is being blocked using sp_who2, examine the BlkBy
column. If any value besides ‘-’ is displayed, it is the SPID of the
process that is holding the blocking lock. In the following output of sp_who2 (edited for space), you can see that process 57 is SUSPENDED, waiting on a lock held by process 53:
exec sp_who2
go
SPID Status Login HostName BlkBy DBName Command
----- ---------- --------- --------------- ----- ----------- ----------------
*** info for internal processes deleted ***
51 sleeping rrankins LATITUDED830-W7 . master AWAITING COMMAND
52 sleeping SQLADMIN LATITUDED830-W7 . msdb AWAITING COMMAND
53 sleeping rrankins LATITUDED830-W7 . bigpubs2008 AWAITING COMMAND
54 sleeping rrankins LATITUDED830-W7 . tempdb AWAITING COMMAND
55 sleeping rrankins LATITUDED830-W7 . master AWAITING COMMAND
56 sleeping SQLADMIN LATITUDED830-W7 . msdb AWAITING COMMAND
57 SUSPENDED rrankins LATITUDED830-W7 53 bigpubs2008 INSERT
58 sleeping rrankins LATITUDED830-W7 . bigpubs2008 AWAITING COMMAND
59 RUNNABLE rrankins LATITUDED830-W7 . master SELECT INTO
To determine what table, page, or rows are involved in blocking and at what level the blocking is occurring, you can query the sys.dm_tran_locks catalog view, as shown in Listing 1.
Listing 1. Viewing Locking Contention by Using the sys.dm_tran_locks View
use bigpubs2008
go
select str(request_session_id, 4,0) as spid,
convert (varchar(12), db_name(resource_database_id)) As db_name,
case when resource_database_id = db_id() and resource_type = 'OBJECT'
then convert(char(12), object_name(resource_Associated_Entity_id))
else convert(char(16), resource_Associated_Entity_id)
end as object,
convert(varchar(12), resource_type) as resource_type,
convert(varchar(8), request_mode) as mode,
convert(varchar(14), resource_description) as resource_desc,
convert(varchar(6), request_status) as status
from sys.dm_tran_locks
order by request_session_id, 3 desc
go
spid db_name object resource_type mode resource_desc status
---- ------------ ---------------- ------------- -------- -------------- ------
52 msdb 0 DATABASE S GRANT
53 bigpubs2008 673416192655360 PAGE IX 1:608 GRANT
53 bigpubs2008 673416192655360 KEY X (928195c101b1) GRANT
53 bigpubs2008 391941215944704 KEY X (59d1a826552c) GRANT
53 bigpubs2008 391941215944704 PAGE IX 1:280 GRANT
53 bigpubs2008 stores OBJECT IX GRANT
53 bigpubs2008 0 DATABASE S GRANT
56 msdb 0 DATABASE S GRANT
57 bigpubs2008 391941215944704 PAGE IS 1:280 GRANT
57 bigpubs2008 391941215944704 KEY S (59d1a826552c) WAIT
57 bigpubs2008 stores OBJECT IS GRANT
57 bigpubs2008 0 DATABASE S GRANT
|
From this output, you can see that Process 57 is waiting for a shared (S) lock on key 59d1a826552c of page 1:280 of the stores table. Process 53 has an intent exclusive (IX) lock on that page because it has an exclusive (X) lock on a key on that page. (Both have the same resource_Associated_Entity_id of 59d1a826552c.)
As an alternative to sp_who and the sys.dm_tran_locks view, you can also get specific information on any blocked processes by querying the sys.dm_os_waiting_tasks system catalog view, as shown in Listing 2.
Listing 2. Viewing Blocked Processes by Using the sys.dm_os_waiting_tasks View
select convert(char(4), session_id) as spid,
convert(char(8), wait_duration_ms) as duration,
convert(char(8), wait_type) as wait_type,
convert(char(3), blocking_session_id) as blk,
resource_description
from sys.dm_os_waiting_tasks
where blocking_session_id is not null
go
spid duration wait_type blk resource_description
---- -------- --------- ---- -----------------------------------------------------
------------------------------------------
57 134118 LCK_M_S 53 keylock hobtid=391941215944704 dbid=8 id=lockfd43800
mode=X associatedObjectId=391941215944704
|
2.2 Setting the Lock Timeout Interval
If you do not want a process to wait indefinitely for
a lock to become available, SQL Server allows you to set a lock timeout
interval by using the SET LOCK_TIMEOUT command. You specify
the timeout interval in milliseconds. For example, if you want your
processes to wait only 5 seconds (that is, 5,000 milliseconds) for a
lock to become available, you execute the following command in the
session:
If your process requests a lock resource that cannot
be granted within 5 seconds, the statement is aborted, and you get the
following error message:
Server: Msg 1222, Level 16, State 52, Line 1
Lock request time out period exceeded.
To examine the current LOCK_TIMEOUT setting, you can query the system function @@lock_timeout:
select @@lock_timeout
go
-----------
5000
If
you want processes to abort immediately if the lock cannot be granted
(in other words, no waiting at all), you set the timeout interval to 0. If you want to set the timeout interval back to infinity, execute the SET_LOCK_TIMEOUT command and specify a timeout interval of -1.
2.3 Minimizing Locking Contention
Although setting the lock timeout prevents a process
from waiting indefinitely for a lock request to be granted, it doesn’t
address the cause of the locking contention. In an effort to maximize
concurrency and application performance, you should minimize locking
contention between processes as much as possible. Some general
guidelines to follow to minimize locking contention include the
following:
Keep transactions as short and concise as
possible. The shorter the period of time locks are held, the less chance
for lock contention. Keep commands that are not essential to the unit
of work being managed by the transaction (for example, assignment
selects, retrieval of updated or inserted rows) outside the transaction.
Keep
statements that comprise a transaction in a single batch to eliminate
unnecessary delays caused by network input/output (I/O) between the
initial BEGIN TRAN statement and the subsequent COMMIT TRAN commands.
Consider
coding transactions entirely within stored procedures. Stored
procedures typically run faster than commands executed from a batch. In
addition, because they are server resident, stored procedures reduce the
amount of network I/O that occurs during execution of the transaction,
resulting in faster completion of the transaction.
Commit
updates in cursors frequently and as soon as possible. Cursor
processing is much slower than set-oriented processing and causes locks
to be held longer.
Note
Even though cursors might run more slowly than
set-oriented processing, cursors can sometimes be used to minimize
locking contention for updates and deletions of a large number of rows
from a table, which might result in a table lock being acquired. The UPDATE or DELETE
statement itself might complete faster; however, if it is running with
an exclusive lock on the table, then no other process can access the
table until it completes. By using a cursor to update a large number of
rows one row at a time and committing the changes frequently, the cursor
uses page- or row-level locks rather than a table-level lock. It might
take longer for the cursor to complete the actual update or delete, but
while the cursor is running, other processes are still able to access
other rows or pages in the table that the cursor doesn’t currently have
locked.
Use the lowest level of locking
isolation required by each process. For example, if dirty reads are
acceptable and accurate results are not imperative, consider using
transaction Isolation Level 0. Use the Repeatable Read or Serializable
Read isolation levels only if absolutely necessary.
Never allow user interaction between a BEGIN TRAN statement and a COMMIT TRAN
statement because doing so may cause locks to be held for an indefinite
period of time. If a process needs to return rows for user interaction
and then update one or more rows, consider using optimistic locking or
Snapshot Isolation in your application.
Minimize
“hot spots” in a table. Hot spots occur when the majority of the update
activity on a table occurs within a small number of pages. For example,
hot spots occur for concurrent insertions to the last page of a heap
table or the last pages of a table with a clustered index on a
sequential key. You can often eliminate hot spots by creating a
clustered index in a table on a column or columns to order the rows in
the table in such a way that insert and update activity is spread out
more evenly across the pages in the table.