An SQL text by Erland Sommarskog, SQL Server MVP.
If you ever tried to track down a blocking situation by running sp_who, DBCC INPUTBUFFER and the various DMV:s while the users are screaming, beta_lockinfo is the thing for you. Note that beta_lockinfo requires SQL 2005 SP2. For previous versions of SQL Server (down to SQL 6.5), use my older aba_lockinfo instead.
beta_lockinfo is a stored procedure that provides information about processes and the locks they hold as well their active transactions. beta_lockinfo is designed to gather as much information about a blocking situation as possible, so that you can instantly find the culprit, and kill the blocking process if the situation is desperate. Then you can sit back and analyse the output from beta_lockinfo to understand how the blocking situation arose and figure out what actions to take to prevent the situation from reoccurring. The output from beta_lockinfo shows all active process as well as passive processes with locks, which objects they lock, the command they last submitted and which statement they are executing. You also get the query plans for the current statements.
Get the source to beta_lockinfo here.
(SourceSafe version 12, checked in
You need to have SQL 2005 SP2 or later to run beta_lockinfo. If you would like to run beta_lockinfo on SQL 2005 RTM or SP1, see at end of the article, which changes you need to make to the code.
You can put the procedure in any database you like. However, the database must be in compatibility mode 90 or later.
If you have used my old aba_lockinfo in the past, you will find that beta_lockinfo is the same in essence, but there are nevertheless several changes. The parameter list is new, and I've abolished the alternative to display all locks. You can only get them aggregated. The parameter @fancy is now called @textmode. By default, process data is only displayed for the first row(s) in grid mode. Some columns in the old result set are replaced with more interesting columns from the DMV:s in SQL 2005. The column memusage is gone entirely. The columns have also been rearranged a bit.
I hope you will appreciate the changes.
I would certainly appreciate to get any feedback on problems you face, be that error messages or incorrect results. Please appreciate that it may be difficult to recreate error situations, as they may relate to conditions at your site. The more information you can include, the better.
I am also interested in hearing from you, if you think that anything should be added to or dropped from the output, or if you have opinions on the output format as such. Given the wealth of information available in the DMV:s in SQL 2005, it has been a delicate affair to select what to include or not.
As for known issues, please see the end of the document.
You reach me at esquel@sommarskog.se.
beta_lockinfo first compiles information about all locks in the system from the DMV sys.dm_tran_locks. In order to make this information more easily digestible, beta_lockinfo aggregates on a number of columns, so if a process holds 10000 locks a big table, you will only see 2-3 lines in the output. beta_lockinfo then unwinds the blocking chain from the DMV sys.dm_os_waiting_tasks.
beta_lockinfo then proceeds to find information about active transactions from sys.dm_tran_session_transactions and sys.dm_tran_active_transactions.
Next beta_lockinfo retrieves process information from a number of places: sys.dm_exec_sessions, sys.sysprocesses (yes, some information is available here only), sys.dm_exec_requests, sys.dm_os_tasks, sys.dm_exec_sql_text and sys.dm_exec_text_query_plan. By default, beta_lockinfo only looks at "interesting" processes, as explained below.
beta_lockinfo then goes on to retrieve the most recently submitted command for each process. The final task before returning the result set is to translate the ids from to sys.dm_tran_locks to object and index names through the new catalog views.
Note that since beta_lockinfo reads the various types of information at different points in time, the information may be out of sync. For instance, a process that held a lock could have exited before beta_lockinfo retrieves the process information. It could also have moved on to a new statement, so the lock information does not match the current statement.
beta_lockinfo takes four parameters, @allprocesses, @textmode, @procdata and @debug, detailed in the table below:
| @allprocesses | If 0 (the default) beta_lockinfo, lists only "interesting" processes. If 1, beta_lockinfo lists all processes. See below for a definition of an "interesting process". | ||||
| @textmode | If 1, the result set is returned to make it easier when you have set SSMS/QA
to return data in text mode: columns are trimmed to be as wide as the widest data
in the column (an idea taken from sp_who2), and there is a blank line inserted between
each process. If 0 (the default), the result set is returned without these extra
thrills. This mode is also known as grid mode, since in you would typically use
this setting when you have selected grid mode in your query tool. In aba_lockinfo, this parameter is called @fancy. | ||||
| @procdata |
| ||||
| @debug | If, 1 beta_lockinfo prints progress information about its operations. If you find that beta_lockinfo takes a long time to run, I'm interested to see the output when @debug = 1, particularly if any other step than compiling lock information needs a lot of time. |
An interesting processes is a processing which is doing something interesting:
Note that since activation procedures for Service Broker runs as system processes (sys.dm_exec_sessions.is_user_process = 0), such a process will not be listed if it holds no other lock than the standard schema-stability lock on the database. Had I not included the condition on is_user_process, beta_lockinfo would also list various background tasks which would be too much noise.
Why would a process be missing from sys.dm_exec_sessions? There are a couple of reasons why this could happen.
Up to SQL 6.5, life was very easy. A process was a connection was a session. With SQL 2005 there are now a whole bunch of different concepts: sessions, execution contexts, requests, connections and tasks. At least. This section describes how beta_lockinfo uses these concepts.
Let's start with a connection, because it's the easiest in this context: beta_lockinfo completely ignores the DMV sys.dm_exec_connections, right or wrong.
The main concept is the session, as listed in sys.dm_exec_sessions. A session is identified with a session_id, or what old-time users of SQL Server knows as a spid.
A session may be idle, or it may be executing one or more requests. A request is a batch of SQL commands or an RPC call to a stored procedure. In the very most cases, a session runs only one at request at a time, but SQL 2005 introduced a new feature, Multiple Active Result Sets, known as MARS. When MARS is in effect, a session may submit a new command batch, even though the previous batch has not completed, and the batches are executed in an interleaved fashion. Requests appear in the view sys.dm_exec_requests. A request is identified by the session_id and the request_id. As long as MARS is not in effect, request_id is usually 0. When MARS is in effect the requests are numbered 1, 2 etc. Beside MARS, I have noticed one more case when request_id is non-zero: requests executed by sessions connected through HTTP endpoints.
An SQL command can be executed single-threaded or by parallel threads. Each such thread is known as an execution context, identified by the exec_context_id. (You may recall that sysprocesses in SQL 2000 has a column with the cryptic name ecid. That's the same thing.) The main thread for the request has exec_context_id = 0, and any non-zero number for a exec_context_id indicates parallel execution. There is no view as such that defines the execution contexts, but all execution contexts have a row in sys.dm_os_tasks. Now, here is a funny thing: if a request with a non-zero request_id requires a parallel plan, the request_id for the threads with a non-zero exec_context_id is nevertheless zero, meaning that if a session has multiple requests running, you cannot tell which request the non-zero execution contexts belong to. (I assume, though, that all contexts belong to the same request, since multiple requests are interleaved, and not truly multi-process.)
A task is something that is listed in sys.dm_os_tasks, and is identified with a varbinary(8) value. All execution contexts, and thus all requests, have a row in sys.dm_os_tasks, but there are also non-session entries in sys.dm_os_tasks. However, these are completely neglected by beta_lockinfo.
In the output, beta_lockinfo includes session_id, exec_context_id
and request_id. But instead of one column for each, beta_lockinfo merges
them into one string with the ids separated by slashes, for instance
In the output for a process, any non-zero requests appear first, with one line per request. Next follow one or more lines with the session_id proper. There is always at least one such line for a session, even if there is no such row for the session in sys.dm_os_tasks. Finally, any execution contexts with a non-zero exec_context_id for the session are listed in order. Occasionally, you may see the same exec_context_id listed twice. This is because two tasks can have the same exec_context_id. (Why it is so, I don't know. Maybe it's a bug. But I've seen it happen.)
Locks are displayed per session. In sys.dm_tran_locks they are listed per execution context and request, but I've opted to collapse them on session level. My experience from aba_lockinfo, which displays locking information for per execution context, is that this generate lots of lines in the output for a massively parallel query, but this mainly serves to make the output difficult to read. And since it's not possible to (easily) determine to which request an execution context belongs to, it was logical to collapse the requests as well. After all, multiple requests should not be that common. (Then again, that may reflect my attitude against MARS, a feature that I find to be of dubious value.)
Finally, there is process, which is not really an SQL Server concept, but which I use in this document somewhat loosely. Sometimes I refer to entire session, sometimes to the unique combination of session, execution context and request.
The key column in the result set is the blklvl column. If this column has
double exclamation marks !! this process is a lead blocker blocking
one of more other processes (which could be another execution context of the same
thread) without being blocked itself. If there is
a number, the process is blocked. 1 indicates that the process is blocked by a
lead blocker, 2 indicates that the process is blocked by a process which in its
turn is blocked by a lead blocker and so on. A process is not involved in
blocking has a blank in this column.
The values in blklvl may be in parentheses. That is, instead of !!
you see (!!). This happens when you have a parallel query, where
the various threads are blocking each other,
but they are not blocked by any other session, nor blocking any other session.
Thus, if you are investigating a blocking issue, you can ignore sessions where
blklvl values are in parentheses.
The column blkby shows which process the process is directly blocked by.
The blkby column is formed as a string with session_id,
exec_context_id and request_id concatenated with slashes, and with the latter
two suppressed if
both are zero. Sometimes you may see a string like
There are two other possible values for blklvl: DD and ??. DD
means that the process is involved in an on-going deadlock. If you look at blkby, and then go that
spid, look at its blkby and so on, you can expect to come back where you started sooner or later. If a
process is blocked by a deadlocked process without being involved in the deadlock itself, blklvl
displays 1, 2 etc as with regular blocking. The value ?? means that the process was listed in
sys.dm_tran_locks as waiting for a lock, but it was not listed in sys.dm_os_waiting_tasks. This could
indicate that by the time beta_lockinfo read sys.dm_os_waiting_tasks, the process had been granted the lock.
To see what a process is waiting for, look at the lstatus and waittype columns. In most cases, a process is waiting for a lock, which is indicated by WAIT or CONVERT in the lstatus column. All non-granted locks appear first in the output for a processes. In the case of parallel execution, the main thread or a sub-thread may be waiting for another thread to complete. In this case waittype will be CXPACKET.
The output from beta_lockinfo includes a couple of columns related to active transactions. Of these, opntrn is taken from sysprocesses and mirrors the current value of @@trancount for the process.
The columns trn_start and trn_since are also straightforward. They reflect when the current transaction started. The first column gives the absolute point in time, while the second tells you for how many seconds the the transaction has been running. This value can in fact be negative, which indicates that the transaction started after beta_lockinfo started running. You can use these columns to determine whether a transaction has been orphaned. This is particularly likely if trn_start is before last_batch, but keep in mind that it's perfectly legal to start a transaction from client level.
The column trninfo holds information from various columns in the transaction DMVs that I preferred to collapse into a single column to make it easier to read. The column can have up to five fields separated by hyphens:
| U/S |
| ||||||||||
| Transaction type |
This field reflects sys.dm_tran_active_transactions.transaction_type.
| ||||||||||
| Transaction state | Taken fom sys.dm_tran_active_transactions.transaction_state. Books Online indicates that the possible values are 0 through 8, but I've seen other values when working with distributed transactions. In most cases you will see 2 here. See the topic for sys.dm_active_transactions in Books Online for a complete listing. | ||||||||||
| DTC state | Taken from sys.dm_tran_active_transactions.dtc_state. This field is only included if this column has a value <> 0, in which case the fields reads one of DTC:ACTIVE, DTC:PREPARED, DTC:COMMITTED, DTC:ABORTED, DTC:RECOVERD. This field indicates that the transaction is distributed, but interesting enough, the transaction type can be DIST, while the dtc_state column still is 0. | ||||||||||
| Bound | If the transaction is a bound transaction (that is, it shares the transaction space with another session), you see BND in the fifth field. (Or more probably the fourth, since I would not expect a bound transaction to have a value in dtc_state.) |
When using MARS, a session may have multiple active transactions. In that case, beta_lockinfo displays information about the oldest transaction.
As outlined above, the result is organised so that there is essentially one row per group of locks. Locks are aggregated on the following columns in sys.dm_tran_locks: resource_type, resource_subtype, resource_database_id, resource_description (application locks only) resource_associated_entity_id (for the resource types it applies to), request_mode, request_status and request_owner_type.
To this come rows for processes with non-zero request ids, listed before the locks, and processes with non-zero execution context ids, listed after the locks. When the parameter @procdata is 'F', process-data is listed only for the first lock row, and it is also listed for any request rows listed before the locks.
In several cases, NULLs and other values that means "nothing" are displayed with blank space to make the output easier to read. I've retained NULL in the cases where I think a NULL has something to tell.
| spid | The process id with the execution-context id and the request id added, if any of them are non-zero, as described above. |
| command | The current command for the request, taken from sys.dm_exec_requests.request_command. This is the same as you see in sp_who, except that for an idle process, you will not see AWAITING COMMAND, but a NULL value. |
| login | The login the process logged into to SQL
Server with. If the process have engaged in impersonation through EXECUTE
AS, either through an explicit statement or by calling a stored procedure
with an EXECUTE AS clause, the currently impersonated login is shown in
parentheses. If the impersonated is a mere database user, you will see a SID
in the parentheses. If the process is a system process (sys.dm_exec_sessions.is_user_process = 0), login reads SYSTEM PROCESS, and any impersonated login is not shown. |
| host | The host the session connected from. Keep in mind that most APIs permits application to set the host name to whatever they like. |
| hostprc | The PID (process id) of the client application. Taken from sys.dm_exec_sessions.host_process_id. |
| endpoint | The endpoint for the process. Most of the time you will see things like TSQL Default TCP, but it could also be the name of for instance a SOAP endpoint. Taken from sys.endpoints.name via sys.dm_exec_sessions.endpoint_id. |
| appl | The name the client application used to identify itself when it connected. |
| dbname | The current database for the process. If there are requests with non-zero ids, it can happen that the request has a different current database than the main session. The database for a running request is taken from sys.dm_exec_sessions.database_id. The session-level database is taken from sys.sysprocesses.dbid. |
| prcstatus | The status of the process. For a running request, this is taken from sys.dm_os_tasks.task_state. For a session without a request, the data is taken from sys.dm_exec_sessions.status. You can easily tell them apart, as former is all uppercase and the latter is all lowercase. |
| spid_ | This is the spid column repeated for convenience. However, there is one case where this column will be NULL, and that is when the session appears in sys.dm_tran_locks only. In this case, only the first spid column has the value. |
| opntrn | The value of @@trancount for the process, blank if this is 0. Note that if a process has a value here, but has NULL in command, and thus is idle, this indicates an orphaned transaction, something which can leads to trouble if the process is holding locks. |
| trninfo | A hyphenated value of five fields giving various tidbits about the transaction. Please see the table above for details. |
| blklvl | When blank the process is
not blocked and is not blocking anyone. Double exclamation marks !!DD
indicates that the process is involved in a deadlock. A
number indicates how far back in the locking queue the process is. 1 means
that the process is blocked by a !!-process or a DD-process, 2 means that the process is
blocked by a process with ?? which means that the process is waiting for a
lock, but was not listed in sys.dm_os_waiting_tasks. |
| blkby | Which process the process is blocked by. A string that is formed from the session id, execution-context id and the request id in the same manner as spid. |
| cnt | The columns following cnt details a group of locks, and the cnt column is the number of lock in such a group. |
| object | The object on which the process has a
lock, or is waiting to get a lock on. In most cases, you will see
database.schema.table.index here. If the lock is on a key in a clustered
index, beta_lockinfo suppresses the index name, to remind you that a key
lock in a clustered index is really a row lock on the data. If you see a number in parentheses, the table is partitioned, and the number is the partition number of the table or index. Even if the table/index is partitioned, beta_lockinfo never shows partition number 1. If the lock is on an allocation unit, beta_lockinfo includes sys.allocation_units.type_desc in brackets in the string. For some resource types, you will only see the database name, because the lock is on database level, or an object which is not schema-bound, for instance an XML schema collection. You may also only see the database name if the entity id in sys.dm_tran_locks did not translate, for instance because the object disappeared while beta_lockinfo was running. If you see table starting with # followed by 8 eight hex digits, this is a table variable, or a temp table that has been dropped and cached. Sometimes you may see things like: dbname.<12345978>. The number in that case is an
object id, that beta_lockinfo was not able to find.For application locks, the value is taken straight from sys.dm_tran_locks.resource_description. |
| rsctype | What sort of resource that is locked. Taken from sys.dm_tran_locks.resource_type. See the topic for sys.dm_tran_locks for details. |
| locktype | What sort of lock the process has taken out/is waiting to get. Taken from sys.dm_tran_locks.request_mode. See the topic Lock Modes in Books Online for details. (The complete path is: SQL Server Database Engine ► Accessing and Changing Database Data ► Locking and Row Versioning ► Locking in the Database Engine ►Lock Modes.) |
| lstatus | Whether the lock has been granted or not. Taken from sys.dm_tran_locks.request_status. In order to make WAIT more visible, beta_lockinfo puts the very common grant in lowercase. |
| ownertype | The entity that owns the lock. Taken from sys.dm_tran_locks.request_owner_type, except that beta_lockinfo shortens SHARED_TRANSACTION_WORKSPACE to STW, as almost all processes has a Sch-S on database level with this owner type. |
| rscsubtype | For some resource types, the column sys.dm_tran_locks.resource_subtype further details what sort of entity that is being locked. I have mainly seen it with the resource type METADATA. |
| waittime | How long the process/request has been waiting in seconds (with three decimals). Blank if the process is not waiting. Taken originally from sys.dm_os_waiting_tasks.wait_duration_ms. |
| waittype | What the process/request is waiting for. Blank if the process is not waiting. Taken from sys.dm_os_waiting_tasks.wait_type. |
| spid__ | Another repetition of the process id. NULL if the process appears in sys.dm_tran_locks only. |
| cpu | Taken from sys.dm_exec_sessions.cpu_time. If the process is running a request, beta_lockinfo shows the value of sys.dm_exec_requests.cpu_time in parentheses. |
| physio | The sum of of the column reads and writes in sys.dm_exec_sessions. If the process is running a request, beta_lockinfo also displays the sum of reads and writes from sys.dm_exec_requests in parentheses. |
| logreads | Taken from sys.dm_exec_sessions.logical_reads. If the process is running a request, beta_lockinfo shows the value of sys.dm_exec_requests.logical_reads in parentheses. |
| now | The value of |
| login_time | When the processes originally logged into SQL Server. Taken from sys.dm_exec_sessions.login_time. |
| last_batch | When the process most recently submitted a command batch. Taken from sys.dm_exec_sessions.last_request_start_time. |
| trn_start | If the process has an active transaction, this is when this transaction started. Taken from sys.dm_tran_active_transactions.transaction_begin_time. |
| last_since | The difference between now and last_batch in seconds, including milliseconds. Blank in this column is special: it means that the value is actually > 20 days. This value can be negative, if the process submitted a batch, after beta_lockinfo started executing, but before it arrived at reading sys.dm_exec_sessions. |
| trn_since | The difference between now and trn_start in seconds, including milliseconds. This value can be negative, if the process started a transaction, after beta_lockinfo started executing. |
| clr | If the process has a CLR object (procedure, function, trigger etc) somewhere on the call stack, this column reads CLR, else it is blank. |
| nstlvl | The current value of @@nestlevel for the process if it is running a request. |
| spid___ | Yet a repetition of the process id. NULL if the process appears in sys.dm_tran_locks only. |
| inputbuffer | The last command sent from the client, taken from DBCC INPUTBUFFER. In text mode, beta_lockinfo replaces newlines with spaces to keep the output on one line. |
| current_sp | The currently executing stored procedure, function or trigger.
This is not necessarily the same as the same procedure that you see in
the inputbuffer column, as this procedure may have invoked another
procedure. The column is blank if the process is not active, or the current
scope is a loose batch of SQL statements. Note that the latter includes
dynamic SQL invoked by |
| curstmt | The statement that the process is currently executing.
If current_sp is non-blank, this is a statement in current_sp.
If current_sp was created WITH ENCRYPTION, beta_lockinfo cannot
retrieve the query text, but instead displays the text "-- ENCRYPTED, pos
start/stop", where start and stop are the positions in
characters for where the statement starts and stops. If you have access to
the source code, you can use these values to determine the statement. The statement is displayed on full on the first row for the process, but for remaining occurrences only the first 50 characters are shown. In text mode, beta_lockinfo replaces newlines with spaces to keep output on a single line. |
| current_plan | The query plan for curstmt
as an XML document.
This column is only presented in grid mode, and is absent in text mode. If an error occurs when retrieving the plan, you will see a document with a single <ERROR>
element. Tip: if you are using SQL Server Management Studio 2005, click the value and it will open in a separate tab. If you then save the document in a file with the extension .sqlplan, you can open the file in Mgmt Studio to see the plan graphically. In SSMS 2008 it's really neat: click the XML document and the graphic plan will open directly, oh-la-la! |
| In text mode there is a final blank column which is used to generate the blank lines to keep different processes apart. |
The data is sorted by spid (with non-zero request ids first, non-zero execution-context ids last), lstatus (with CONVERT and WAIT first), object and rsctype.
Unfortunately, beta_lockinfo is not as lean as I would like it to be. The chief
reason for this is that access to sys.dm_tran_locks is nowhere cheap when
there are plenty of locks on the system. I had a situation with 2.3 million
locks, and a mere SELECT
Of the other parameters @textmode = 1, will take some more resources that you may want to avoid on a swamped server.
If your system is really hogged, and you just want to know who is creating all this mess, try this:
SELECT request_session_id, COUNT(*) FROM sys.dm_tran_locks GROUP BY request_session_id
It appears that as long as you do not include any character columns in the
output, the GROUP BY operation does not take that much more time than a plain
SELECT
The version of beta_lockinfo I have posted here, does not run on SQL 2005 RTM and SP1. If you still want to run beta_lockinfo on these versions of SQL Server, this section details the changes you need to make to get it work.
First find these two lines:
coalesce(nullif(es.original_login_name, ''),
suser_sname(es.original_security_id)),
Replace with:
coalesce(nullif(es.login_name, ''), suser_sname(es.security_id)),
(This is the same as the line above.) If there has been impersonation, you will only see the currently impersonated login in the column login; the original login is not available.
Next find these two lines:
OUTER APPLY sys.dm_exec_text_query_plan(er.plan_handle,
er.statement_start_offset, er.statement_end_offset) AS etqp
Here you have two choices. Either delete this line entirely, and in such case you should change the line reading
convert(xml, etqp.query_plan),
To simply read "NULL,". You can also change the OUTER APPLY to read
OUTER APPLY sys.dm_exec_query_plan(er.plan_handle)AS etqp
If you do this, current_plan will still have a value, but for a stored procedure that will be the plans for all statements in the procedure.
I did not want to put out a proper version of beta_lockinfo for RTM and SP1, since I've not performed any serious testing on these versions.
If you have questions or comments and not the least suggestions for improvements just mail me, Erland Sommarskog, on esquel@sommarskog.se.
| |
| |
| Fixed bug that caused a NOT NULL violation when there was a lock on a dropped allocatoin unit. | |
| |
| |
| No changes, but I hereby declare that beta_lockinfo has passed the test well enough to no longer be regarded as a beta version. | |
| |
|
|
| First release of beta_lockinfo. |