LOCK ESCALATION
When more than 5,000 locks are taken
out on the rows or pages of a particular table within a single T-SQL
statement, lock escalation is triggered. During this process, the
intent lock at the higher level is converted to a full lock — assuming
this is possible and not prevented by other locks that may be already
acquired — and then the locks at the more granular levels can be
released, freeing up the resources needed to manage them.
As explained earlier, when a lock is taken out on
a row or page, intent locks are taken out on the items higher up in the
lock hierarchy — in particular, on the HoBTs and tables related to the
locked row/page. In addition to providing a shortcut to determining
whether something might be locking part of the table, these intent
locks provide escalation points if the overhead of maintaining the
locks becomes too high.
Escalation is to either the HoBT (for partitioned
tables) or to the table itself (which is more typical). A page lock is
not considered an escalation point — probably because by the time 5,000
locks are taken out, quite a large number of pages are locked, and a
full table lock is a sensible solution to be able to reduce the number
of locks.
If escalation can’t occur, the more granular
locks can’t be released, and everything continues as before, with locks
being taken out at the more granular points. This is typically because
of other activity occurring in the affected table. Escalation will be
attempted each time another 1,250 locks are acquired.
Lock escalation can be prevented by
setting a table option to disallow it, or by forcing queries to take
out table locks to start with. Ideally, you should let the system
escalate locks as required, and only consider this kind of action when
the number of escalations (monitored through Lock:Escalation
events) becomes significantly higher than expected (compared to a
benchmark of your system in a healthy state). You can also use trace
flags (1211 and 1224) to disable lock escalation.
DEADLOCKS
Ideally, despite locks, your database
system will allow a lot of users at once, and each transaction will get
in, make the single change needed, and get out again; but locks
inevitably mean blocking, and when transactions need to do multiple
operations, this locking can even lead to deadlocks.
Although your application users will report that
the application has deadlocked, this kind of behavior does not actually
mean a deadlock has occurred. When a deadlock has been detected, the
Database Engine terminates one of the threads, resolving the deadlock.
The terminated thread gets a 1205 error, which conveniently suggests
how to resolve it:
Error 1205 : Transaction (Process ID) was deadlocked on resources with another
process and has been chosen as the deadlock victim. Rerun the transaction.
Indeed, rerunning the transaction is
often the best course of action here, and hopefully your application or
even your stored procedure will have caught the error, recognized that
it is a 1205, and tried the transaction again. Let’s consider how a
deadlock occurs, though.
It’s quite straightforward really — one
transaction locks a resource and then tries to acquire a lock on
another resource but is blocked by another transaction. It won’t be
able to finish its transaction until such time as this second
transaction completes and therefore releases its locks. However, if the
second transaction does something that needs to wait for the first
transaction, they’ll end up waiting forever. Luckily this is detected
by the Database Engine, and one of the processes is terminated.
When diagnosing these kinds of problems, it’s worth considering that there are useful trace events such as Lock:Deadlock
and Deadlock graph events. This enables you to see which combination of
resources was being requested, and hopefully track down the cause. In
most cases, the best option is to help the system get the quickest
access to the resources that need updating. The quicker a transaction
can release its resources, the less likely it is to cause a deadlock.
However, another option is to lock up additional resources so that no
two transactions are likely to overlap. Depending on the situation, a
hint to lock an entire table can sometimes help by not letting another
transaction acquire locks on parts of the table, although this can also
cause blocking that results in transactions overlapping, so your
mileage may vary.