3. Index Locking
As with locks on data pages, SQL Server manages locks
on index pages internally. There is the opportunity for greater locking
contention in index pages than in data pages. Contention at the root
page of the index is the highest because the root is the starting point
for all searches via the index. Contention usually decreases as you move
down the various levels of the B-tree, but it is still higher than
contention at the data page level due to the typically greater number of
index rows per index page than data rows per data page.
If locking contention in the index becomes an issue, you can use ALTER INDEX to manage the locking behavior at the index level. The syntax of this command is as follows:
ALTER INDEX { index_name | ALL } ON object
{ ALLOW_ROW_LOCKS = { ON | OFF }
| ALLOW_PAGE_LOCKS = { ON | OFF }
The default for both ALLOW_ROW_LOCKS and ALLOW_PAGE_LOCKS is ON.
When both of these options are enabled, SQL Server automatically makes
the decision whether to apply row or page locks on the indexes and can
escalate locks from the row or page level to the table level. When ALLOW_ROW_LOCKS is set to OFF, row locks on indexes are not used. Only page- or table-level locks are applied. When ALLOW_PAGE_LOCKS is set to OFF, no page locks are used on indexes, and only row- or table-level locks are applied. When ALLOW_ROW_LOCKS and ALLOW_PAGE_LOCK are both set to OFF, only a table-level lock is applied when the index is accessed.
Note
When ALLOW_PAGE_LOCKS is set to OFF for an index, the index cannot be reorganized.
SQL Server usually makes good choices for the index
locks, but based on the distribution of data and nature of the
application, you might want to force a specific locking option on a
selective basis. For example, if you are experiencing a high level of
locking contention at the page level of an index, you might want to
force SQL Server to use row-level locks by turning off page locks.
As another example, if you have a lookup table that
is primarily read-only (for example, one that is only refreshed by a
weekly or monthly batch process), it may be more efficient to turn off
page and row locking so that all readers simply acquire shared
table-level locks, thereby reducing locking overhead. When the weekly or
monthly batch update runs, the update process acquires an exclusive
table-level lock when refreshing the table.
To display the current locking option for a given index, you use the INDEXPROPERTY function:
select INDEXPROPERTY(object_ID , index_name,
{ 'IsPageLockDisallowed' | 'IsRowLockDisallowed' } )
Caution
SQL Server generally makes the correct decision in
choosing the appropriate locking granularity for a query. It is
generally not recommended that you override the locking granularity
choices that the Query Optimizer makes unless you have good reason to do
so and have evaluated all options first. Setting the inappropriate
locking level can adversely affect the concurrency for a table or index.
4. Row-Level Versus Page-Level Locking
For years, it was often debated whether row-level
locking was better than page-level locking. That debate still goes on in
some circles. Many people argue that if databases and applications are
well designed and tuned, row-level locking is unnecessary. This is borne
out somewhat by the number of large and high-volume applications that
were developed when row-level locking wasn’t even an option. (Prior to
SQL Server version 7, the smallest unit of data that SQL Server could
lock was the page.) However, at that time, the page size in SQL Server
was only 2KB. With page sizes expanded to 8KB, a greater number of rows
(four times as many) can be contained on a single page. Page-level locks
on 8KB pages could lead
to greater page-level contention because the likelihood of the data
rows being requested by different processes residing on the same page is
greater. Using row-level locking increases the concurrent access to the
data.
On the other hand, row-level locking consumes more
resources (memory and CPU) than page-level locks simply because there is
a greater number of rows than pages in a table. If a process needed to
access all rows on a page, it would be more efficient to lock the entire
page than acquire a lock for each individual row. This would result in a
reduction in the number of lock structures in memory that the Lock
Manager would have to manage.
Which is better—greater concurrency or lower overhead? As shown earlier, in Figure 37.6,
it’s a trade-off. As lock size decreases, concurrency improves, but
performance degrades due to the extra overhead. As the lock size
increases, performance improves due to less overhead, but concurrency
degrades. Depending on the application, the database design, and the
data, either page-level or row-level locking can be shown to be better
than the other in different circumstances.
SQL Server makes the determination automatically at
runtime—based on the nature of the query, the size of the table, and the
estimated number of rows affected—of whether to initially lock rows,
pages, or the entire table. In general, SQL Server attempts to first
lock at the row level more often than the page level, in an effort to
provide the best concurrency. With the speed of today’s CPUs and the
large memory support, the overhead of managing row locks is not as
expensive as in the past. However, as the query processes and the actual
number of resources locked exceed certain thresholds, SQL Server might
attempt to escalate locks from a lower level to a higher level, as
appropriate.
At times, SQL Server might choose to do both row and
page locking for the same query. For example, if a query returns
multiple rows, and if enough contiguous keys in a nonclustered index
page are selected to satisfy the query, SQL Server might place page
locks on the index while using row locks on the data. This reduces the
need for lock escalation.
5. Lock Escalation
When SQL Server detects that the locks acquired by a
query are using too much memory and consuming too many system resources
for the Lock Manager to manage the locks efficiently, it automatically
attempts to escalate row, key, or page locks to table-level locks. For
example, because a query on a table continues to acquire row locks and
every row in the table will eventually be accessed, it makes sense for
SQL Server to escalate the row locks to a table-level lock. After the
table-level lock is acquired, the row-level locks are released. This
helps reduce locking overhead and keeps the system from running out of
available lock structures. While the default behavior in SQL Server is to escalate
to table-level locks, SQL Server 2008 introduces the capability to
escalate row or page locks to a single partition via the LOCK_ESCALATION setting in ALTER TABLE. This new option allows you to specify whether escalation is always to the table or partition level. The LOCK_ESCALATION setting can also be used to prevent lock escalation entirely.
Note
SQL Server never escalates row locks to page locks,
only to table or partition-level locks. Also, multiple partition-level
locks are never escalated to a single table-level lock.
What are the lock escalation thresholds in SQL
Server? Currently, SQL Server attempts lock escalation under the
following conditions:
Whenever a single T-SQL statement acquires at
least 5,000 locks on a single reference of a table, table partition, or
index (this value is subject to change in subsequent service packs).
Note that lock escalation does not occur if the locks are spread across
multiple objects in the same statement—for example, 4,000 locks on one
index and 2,500 locks on another.
When the amount of memory required by lock resources exceeds 40% of the available Database Engine memory pool
Note
Generally, if more memory is required for lock
resources than is currently available in the Database Engine memory
pool, the Database Engine allocates additional memory dynamically to
satisfy the request for locks as long as more computer memory is
available and the max server memory threshold has not been
reached. However, if allocating additional memory would cause paging at
the operating system level, more lock space is not allocated. If no more
memory is available, or the amount of memory allocated to lock
resources reaches 60% of the memory acquired by an instance of the
Database Engine, further requests for locks generate an out-of-lock
memory error.
If locks cannot be escalated because of lock
conflicts, SQL Server reattempts lock escalation when every 1,250
additional locks are acquired. For example, if another process is also
holding locks at the page or row level on the same table (indicated by
the presence of that process’s intent lock on the table), lock
escalation cannot take place if the lock types are not compatible until
the lower-level locks are released by the other processes. In this case,
SQL Server continues acquiring locks at the row or page level until the
table lock becomes available.
Controlling Lock Escalation
Escalating locks to the table or partition level can
lead to locking contention or blocking for other transactions attempting
to access a row or page in the same table. Under certain circumstances,
you might want to disable lock escalation.
As mentioned previously, lock escalation can be enabled or disabled at the table level using the ALTER TABLE command:
ALTER TABLE tablename set (LOCK_ESCALATION ={ AUTO | TABLE | DISABLE } )
Setting the option to AUTO allows SQL Server to escalate to the table or partition level. Setting the option to DISABLE prevents escalation to the table or partition level.
SQL Server 2008 also supports disabling lock
escalation for all tables in all databases within a SQL Server instance
using either the 1211 or 1224 trace flags. Trace flag 1211 completely
disables lock escalation, regardless of the memory required for lock
resources. However, when the amount of memory required for lock
resources exceeds 60% of the maximum available Database Engine memory,
an out-of-lock memory error is generated. Alternatively, trace flag 1224
disables the built-in lock escalation based on the number of locks
acquired, but lock escalation is still possible when the 40% of
available Database Engine memory threshold is reached. However, as noted
previously, if the locks cannot be escalated, SQL Server could still
run out of available memory for locks.
Note
You should be extremely
careful when considering disabling lock escalation via the trace flags. A
poorly designed application could potentially exhaust the available SQL
Server memory with excessive lock structures and seriously degrade SQL
Server performance. It is usually preferable to control lock escalation
at the object level via the ALTER TABLE command.