You’ve just read about
blocking other users from seeing what’s going on, or jumping in with
other changes that stop a transaction from being able to behave in
proper isolation. The mechanism in SQL Server that is used for this is
a lock. By locking a piece of data, SQL Server prevents access to it.
As you might expect, there are a variety of lock types (known as lock
modes), and a variety of types of access they are designed to prevent.
A lock is needed for almost every kind of data access, even reads,
which means that locks actually do their blocking against other lock
types. We don’t say that an exclusive lock blocks reads; we say that an
exclusive lock is incompatible with a shared lock — but the effect is
the same. To picture the kind of blocking that will occur, imagine the
kind of access that needs to take out the incompatible locks.
There are also many different types of things
that can be locked. These are known as lock resources. By identifying
what is locked, what caused it to be locked, and the type of lock that
is taken out, you have the full set of information about the lock.
Monitoring Locks
Two main dynamic management views (DMVs) are used to monitor locks: sys.dm_tran_locks and sys.dm_os_wait_stats.
The former lists all the locks that have currently been taken, and
includes information identifying the lock resource and more, while the
latter lists information about how often processes have had to wait
when acquiring the various lock types.
The DMV sys.dm_tran_locks
returns a lot of useful information about the locks currently held in
the SQL Server instance. It shows not only the lock resource (as per
the list of lock resources described next) and lock mode (also
discussed later), but also the ID of the database in which the lock is
located, plenty of information to identify the resource that has been
locked, whether the lock was actually granted (it could be being
converted, or it could be waiting), how many locks of that type are on
the resource, the sessionid, and more.
There are a lot of columns, as described in Table 1.
TABLE 1: Currently Active Lock Resource Information Returned by sys.dm_tran_locks
COLUMN NAME |
DESCRIPTION |
resource_type |
The type of lock resource that a transaction is trying to take a lock on, such as OBJECT, PAGE, KEY, etc. |
Resource_subtype |
Provides a subclassification of the
resource requested. Not mandatory, but good for qualifying the
resource; for example, if you create a table in a transaction you will
get a subtype of DDL on the DATABASE resource_type lock. |
Resource_database_id |
The database in which the resource was requested |
Resource_description |
Contains information describing the resource that isn’t available in any other column |
Resource_associated_entity_id |
Describes the entity upon which the lock is being requested. It can be one of three things depending on the resource type: Object ID, HoBT ID, or Allocation Unit ID. |
Resource_lock_partition |
Normally 0. Lock partitioning must be
available to you in order to see anything in this column, and only
available on machines with 16 cores presented. It applies only to
object locks, and even then only to those without a resource_subtype. |
Request_mode |
The mode in which the lock is requested.
If the lock has a status of granted, then this is the lock mode under
which the resource is currently operating — for example, IX (Intent
Exclusive), X (Exclusive), etc. |
Request_type |
This value is always LOCK because this view only supports locks. |
Request_status |
This is one of three values: |
|
GRANT: The requested lock is in effect. |
|
WAIT:
The lock is prevented from being acquired (blocked) because the
resource is already locked with an incompatible locking mode. For
instance one connection has a Grant X (Exclusive) lock on the object,
and you are trying to also acquire an exclusive lock on the same object. |
|
CONVERT:
The lock was previously granted with another status and is trying to
upgrade to a more restrictive mode but is currently being blocked from
doing so. |
Request_reference_count |
An approximate count of the number of times that a requestor has requested a lock on the given resource |
Request_session_id |
In most cases this is the session that requested the resource. |
|
Two special values: |
|
-2: A distributed transaction with no enlisted sessions |
|
-3: A deferred recovery transaction |
Request_exec_context_id |
Execution context of the process that owns the request |
Request_request_id |
Batch ID of the request that owns the resource |
Request_owner_type |
The entity type of the owner of the request. Possible types are as follows: |
|
TRANSACTION |
|
CURSOR |
|
SESSION |
|
SHARED_TRANSACTION_WORKSPACE |
|
EXCLUSIVE_TRANSACTION_WORKSPACE |
Request_owner_id |
Used when the owner type is TRANSACTION and represents the transaction ID |
Request_owner_guid |
Used when the owner type is TRANSACTION
and the request has been made by a distributed transaction. In that
circumstance, the value equates to the MSDTC GUID for that transaction. |
Lock_owner_address |
Represents the in-memory address of the request. Use this column to join to the resource_address column in sys.dm_os_waiting_tasks to see blocking lock information. |
The DMV sys.dm_os_wait_stats
shows the wait stats for the locks by their mode , and you can see these in the wait_type column, with values such as LCK_M_IX for IX-locks, and LCK_M_S for S-locks. For each wait_type,
the number of times waits have been required is shown, along with the
total and maximum wait times and the total signal wait time. Using this
DMV can highlight when the Database Engine must wait to acquire the
various locks.
Lock Resources
Table 2
describes the many different types of things that can be locked, known
as lock resources. It also gives an example of what each type of
resource might look like.
TABLE 2: List of Lock Resources and Examples
RESOURCE TYPE |
EXAMPLE OF RESOURCE |
DESCRIPTION |
RID |
1:8185:4 |
A row identifier used to lock a single row when the table in question is a heap |
|
|
The RID format can be understood as: <File : Page : Slot ID> |
|
|
The lock resource RID can be retrieved with the undocumented %%lockres%% function. |
KEY |
(3a01180ac47a) |
A lock on a single row on an index. This
includes row locks taken on tables that have a clustered index on them.
The resource is a hash value that can be retrieved against your table
with %%lockres%%. |
PAGE |
1:19216 |
A lock on an index or data page. Breaks down as <FILE ID>:<PAGE NUMBER>. |
|
|
These map to the file_id and page_id fields in the sys.dm_os_buffer_descriptors DMV. |
EXTENT |
1:19216 |
A contiguous set of eight pages. Pages are allocated to tables in extents. Breaks down as <FILE ID> : <FIRST PAGE NO> |
HoBT |
72057594058637312 |
HoBT
is a Heap or Balanced Tree (BTree). When a table is a heap (no
clustered index), it protects the heap. Otherwise, it protects the
BTree of the index. |
OBJECT |
2105058535 |
Normally a table lock but it could be anything with an OBJECT_ID. If it’s a table lock, then it covers both data pages and all indexes on the table. |
APPLICATION |
0:[MyAppLock]: (6731eaf3) |
An application lock. Set by sp_getapplock. |
METADATA |
xml_collection_id = 65536 |
Used to lock SQL Server system metadata
— e.g., when taking a schema stability lock on metadata of an XML
column when querying a row. |
ALLOCATION_UNIT |
72057594039828480 |
Allocation Unit ID seen during deferred
drop operations, such as on a large table. Also visible during
minimally logged operations such as SELECT INTO. |
FILE |
0 |
Seen when adding or removing files from a database. No resource description information is published. |
DATABASE |
7 |
A lock against the entire database. This
can be a shared transaction workspace lock to identify a connection in
the DB or a transaction lock when altering the database. Changing from read_write to read_only requires an exclusive transaction against the database. |
You may look at this table with a degree of hope
that your locks never end up too far down the list. It’s quite
understandable and reasonable to expect that your normal querying
behavior should be able to get away with just locking rows, pages, and
occasionally a whole HoBT; but remember that a single object’s locks
can cover many HoBT locks, which in turn, might cover thousands or
millions of pages, and who knows how many rows. A trade-off must be
made between having a smaller number of locks with more data locked
than strictly necessary and having less data locked with a larger
number of locks.
Lock escalation occurs when a number of
locks are converted into a smaller number of locks at levels further
down that list (typically to the object level) — that is, making the
trade-off to reduce the number of locks through coarser granularity.
This can be beneficial in that it reduces the amount of overhead to
manage the locks; but of course with more data locked, there is a
higher likelihood of processes being blocked by encountering locked
data.