{"id":1001,"date":"2010-09-30T00:00:00","date_gmt":"2010-09-30T00:00:00","guid":{"rendered":"https:\/\/test.simple-talk.com\/uncategorized\/investigating-transactions-using-dynamic-management-objects\/"},"modified":"2021-08-24T13:40:26","modified_gmt":"2021-08-24T13:40:26","slug":"investigating-transactions-using-dynamic-management-objects","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/database-administration-sql-server\/investigating-transactions-using-dynamic-management-objects\/","title":{"rendered":"Investigating Transactions Using Dynamic Management Objects"},"content":{"rendered":"<div id=\"pretty\">\n<p class=\"start\">Every statement executed against SQL Server is transactional, and SQL Server implements various transaction isolation levels, to ensure the ACID (Atomicity, Consistency, Isolation, and Durability) properties of these transactions. In practical terms, this means that it uses locks and latches to mediate transactional access to shared database resources, and so prevent &#8220;interference&#8221; between the transactions. When a transaction encounters a locked resource it must, of course, wait for the resource to become free before proceeding. If such blocking occurs frequently or is protracted, it greatly restricts the number of concurrent users that can access the system.<\/p>\n<p>This article explains how to use the transaction-related Dynamic Management Objects (DMOs) to locate the transactions that are causing locking and blocking issues on their SQL Server instances, and the sessions to which they belong. Having located the offending transactions, the DBA can then take steps to alleviate the blocking, either by tuning the SQL or, if this is not possible, by careful scheduling of the more resource-hungry and\/or longer-running business reports, so that they occur separately from the main Online Transaction Processing (OLTP) load.<\/p>\n<p>We&#8217;ll also examine how to use the transaction-related DMOs to, for example, investigate long-running transactions that may be preventing transaction log truncation, and transactions that are causing large amounts of data to be written to the transaction log, making it grow rapidly in size.<\/p>\n<h1>What is a transaction, anyway?<\/h1>\n<p>Microsoft Books Online is as good a place as any to start, for a definition of the term, transaction:<\/p>\n<p><i>A transaction is a sequence of operations performed as a single logical unit of work. A logical unit of work must exhibit four properties, called the ACID properties, to qualify as a transaction.<\/i><\/p>\n<p>For a transaction to pass the ACID Test, all of its data modifications must complete or be rolled back (<i>Atomic<\/i>); the end result must be that all data and supporting structures such as indexes must be consistent with the rules that apply to them (<i>Consistent<\/i>). A transaction cannot be impacted by any other transactions occurring concurrently (<i>Isolated<\/i>); the results of the transaction being permanently recorded in the RDBMS (<i>Durability<\/i>).<\/p>\n<p>As noted in the introduction to this article, every statement executed against SQL Server is transactional. If we issue a single SQL statement, an implicit transaction is run under the covers, which auto-starts and auto-completes. If we use explicit <span class=\"STCodeinTextChar\"> BEGIN<\/span> <span class=\"STCodeinTextChar\"> TRAN<\/span> \/ <span class=\"STCodeinTextChar\"> COMMIT<\/span> <span class=\"STCodeinTextChar\"> TRAN<\/span> commands, then we can group together in an explicit transaction a set of statements that must fail or succeed together. This is easily demonstrated by the series of queries shown in Listing 1 and the resulting output.<\/p>\n<pre>\tSELECT\u00a0 DTAT.transaction_id\r\n\tFROM\u00a0\u00a0\u00a0 sys.dm_tran_active_transactions DTAT\r\n\tWHERE\u00a0\u00a0 DTAT.name  &lt;&gt;  'worktable' ; \r\n\t\r\n\tSELECT\u00a0 DTAT.transaction_id\r\n\tFROM\u00a0\u00a0\u00a0 sys.dm_tran_active_transactions DTAT\r\n\tWHERE\u00a0\u00a0 DTAT.name  &lt;&gt;  'worktable' ; \r\n\t\r\n\tBEGIN  TRAN \r\n\tSELECT\u00a0 DTAT.transaction_id\r\n\tFROM\u00a0\u00a0\u00a0 sys.dm_tran_active_transactions DTAT\r\n\tWHERE\u00a0\u00a0 DTAT.name  &lt;&gt;  'worktable' ; \r\n\t\r\n\tSELECT\u00a0 DTAT.transaction_id\r\n\tFROM\u00a0\u00a0\u00a0 sys.dm_tran_active_transactions DTAT\r\n\tWHERE\u00a0\u00a0 DTAT.name  &lt;&gt;  'worktable' ; \r\n\tCOMMIT TRAN\r\n\t\r\n\ttransaction_id\r\n\t--------------------\r\n\t18949550\r\n\t...\r\n\t18949551\r\n\t...\r\n\t18949552\r\n\t...\r\n\t18949552\r\n<\/pre>\n<p class=\"caption\">Listing 1: All statements within SQL Server are transactional.<\/p>\n<p>According to the results of these queries, any statements executed outside of an explicit transaction will execute as separate transactions, and each will result in a row with a unique <span class=\"STCodeinTextChar\"> transaction_id<\/span> in our result sets. All statements executed within an explicit transaction will be reported with a single <span class=\"STCodeinTextChar\"> transaction_id<\/span>.<\/p>\n<div class=\"pullout\">\n<p>This article is adapted from a chapter of &#8216;Performance Tuning with SQL Server Dynamic Management Views&#8217; by Louis Davidson and Tim Ford. To learn more about this book, <a href=\"http:\/\/www.amazon.com\/gp\/product\/1906434476\/\">visit the Amazon page<\/a>.<\/p>\n<\/div>\n<div>\n<p>SQL Server will attempt to ensure that each unit of work, be it a single-statement implicit transaction, or any number of individual SQL statements within an explicit transaction, conforms to the ACID test characteristics.<\/p>\n<p>What we hope to demonstrate, in this article, is how to observe these units of work via the DMOs. Since the lifespan of these transactions is measured in milliseconds, when everything is going right, the focus will be on those transactions that are having difficulty completing in a timely fashion, whether due to contention for resources, poor tuning, or other issues.<\/p>\n<h1>Investigating Locking and Blocking<\/h1>\n<p>Locking is an integral aspect of any RDBMS. Locks control how transactions are allowed to interact, impact, and impede one another when running simultaneously against common objects. Unless you restrict data access to one user at a time, clearly not a viable option, locks are necessary to the smooth functioning of any RDBMS.<\/p>\n<p>Locks are to be neither feared nor shunned but, nevertheless, can cause problems for the reckless or unwary. When using SQL Server&#8217;s default isolation level, <span class=\"STCodeinTextChar\"> READ<\/span> <span class=\"STCodeinTextChar\"> COMMITTED<\/span>, <b>shared read<\/b> locks are acquired during data reads. These locks prevent another transaction from modifying that data while the query is in progress, but do not block other readers. Furthermore, &#8220;dirty reads&#8221; are forbidden so SQL Server acquires <b>exclusive locks<\/b> during updates, to prevent a transaction from reading data that has been modified by another transaction, but not committed. Of course, this means that if one transaction (A) encounters data that is being modified by another transaction (B), then transaction A is blocked; it needs access to a resource that is locked by B and cannot proceed until B either commits or rolls back.<\/p>\n<p>As noted, this is normal behavior but, in conditions of highly concurrent user access, the potential for blocking will increase. The situation will be exacerbated by, for example, transactions that are longer than they need to be or are poorly written, causing locks to be held for longer than necessary. As locking and blocking increase, so does the overhead on the RDBMS and the end result is a significant reduction in concurrency.<\/p>\n<p class=\"style17\"><b>READ UNCOMMITTED &#8211; Don&#8217;t do it<\/b><br \/>\n If you want concurrency at all costs, then <span class=\"STCodeinTextChar\"> READ<\/span> <span class=\"STCodeinTextChar\"> UNCOMMITTED<\/span> isolation level will shun locks as far as possible. This mode allows dirty reads, so use it at the expense of your data consistency and integrity.<\/p>\n<p>In <span class=\"STCodeinTextChar\"> READ<\/span> <span class=\"STCodeinTextChar\"> COMMITTED<\/span> mode, shared read locks are released as soon as a query completes, so data modification transactions can proceed at that point, even if the transaction to which the query belongs is still open. Therefore, non-repeatable reads are possible; if the same data is read twice in the same transaction, the answer may be different. If this is not acceptable, the isolation level can be made more restrictive. The <span class=\"STCodeinTextChar\"> REPEATABLE<\/span> <span class=\"STCodeinTextChar\"> READ<\/span> level will ensure that all the rows that were read cannot be modified or deleted until the transaction which read them completes. However, even this level does not prevent new rows(called phantom rows) being inserted that satisfy the query criteria, meaning that reads are not, in the true sense, repeatable. To prevent this, you could switch to <span class=\"STCodeinTextChar\"> SERIALIZABLE<\/span>, the most restrictive isolation level of all, which basically ensures that a transaction is completely isolated from the effects of any other transaction in the database.<\/p>\n<p>However, as the isolation level becomes more restrictive, so the use of locks becomes more prevalent and the likelihood of blocking, and even deadlocking, where transaction A is waiting for a resource that is held by B, and vice versa, increases dramatically. Note also, that it is not only competing modifications that can cause a deadlock. It is just as possible for a modification to deadlock with a reporting query. In short, DBAs need a way of investigating blocking on their SQL Server instances.<\/p>\n<p><b>Snapshot isolation level<\/b><br \/>\n The snapshot isolation level was introduced in SQL Server 2005 and eliminates blocking and deadlocking by using row versioning in the <span class=\"STCodeinTextChar\"> tempdb<\/span> version store to maintain concurrency, rather than establishing locks on database objects. There exist five DMOs dedicated to investigation of this isolation level and of version store usage. These will not be covered in this article, but more information can be found in our book <a href=\"http:\/\/www.amazon.com\/Performance-Tuning-Server-Dynamic-Management\/dp\/1906434476\"> Performance Tuning with SQL Server DMVs<\/a>, as well as in <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/ms178621.aspx\">Books Online<\/a>.<\/p>\n<h2>DMOs, Activity Monitor and sp_who2<\/h2>\n<p>Pre-SQL Server 2005, the only way a DBA could analyze blocking behavior in a SQL Server instance was to query the <span class=\"STCodeinTextChar\"> sysprocesses<\/span> system table or to use <span class=\"STCodeinTextChar\"> sp_who<\/span> and <span class=\"STCodeinTextChar\"> sp_who2<\/span>. With SQL 2005, the situation has improved dramatically, and this information is now available through several new routes:<\/p>\n<ul>\n<li>using the <span class=\"STCodeinTextChar\"> sys.dm_trans_locks<\/span> DMV<\/li>\n<li>the &#8220;blocked process report&#8221; in SQL Server Profiler<\/li>\n<li>using Activity Monitor in SSMS<\/li>\n<\/ul>\n<p>Activity Monitor is basically a graphical representation of <span class=\"STCodeinTextChar\"> sysprocesses<\/span> and it can provide basic information regarding sessions that are blocked and blocking. For example, if we establish some blocking on a table in <span class=\"STCodeinTextChar\"> AdventureWorks<\/span>, then open Activity Monitor (<span class=\"STItalic\">Ctrl+Alt+A<\/span>), we can quickly establish that session 54 is blocked by session 52, as shown in Figure 1.<\/p>\n<p class=\"illustration\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1151-DMV1.jpg\" alt=\"1151-DMV1.jpg\" \/><\/p>\n<p class=\"caption\">Figure 1: Investigating blocking with Activity Monitor<\/p>\n<p class=\"MsoNormal\">However, the information provided is pretty limited and, as we hope to demonstrate, the <span class=\"STCodeinTextChar\"> sys.dm_trans_lock<\/span> DMV provides a wealth of extra information regarding the lock being held; you can query the available data in such a way that everything you need to resolve the problem is presented in a single result set.<\/p>\n<h2>An overview of the sys.dm_tran_locks DMV<\/h2>\n<p>The <span class=\"STCodeinTextChar\"> sys.dm_tran_locks<\/span> DMV provides insight into the current state of locking in a SQL Server instance, across all databases. It returns a row for every <span class=\"STItalic\"> currently active request to the lock manager for a lock that has been granted or is waiting to be grant<\/span>ed. The columns provided offer information regarding both the resource on which the lock is being held (or has been requested), and the owner of the request. The <span class=\"STCodeinTextChar\"> sys.dm_tran_locks<\/span> DMV provides a snapshot of the state of the server at the point in time the query is executed.<\/p>\n<p>The resource-related columns identify the resource being locked, its type, and the database on which the locking is occurring.<\/p>\n<ul>\n<li><b>resource_type<\/b> &#8211; target resource object for the lock, such as <span class=\"STBold\">Table <\/span> <span class=\"STBold\"> (<\/span><span class=\"STCodeinTextChar\">OBJECT<\/span><span class=\"STBold\"> locks)<\/span>, <span class=\"STBold\">Page (<\/span><span class=\"STCodeinTextChar\">PAGE<\/span><span class=\"STBold\"> locks)<\/span>, <span class=\"STBold\">Row (<\/span><span class=\"STCodeinTextChar\">RID<\/span><span class=\"STBold\"> locks)<\/span>, or <b> Key<\/b> (<span class=\"STCodeinTextChar\">KEY<\/span> locks).<\/li>\n<li><span class=\"STCodeinTextBold\"> resource_database_id<\/span> &#8211; ID of the database on which the locked resource resides. This column can be used to join to the <span class=\"STCodeinTextChar\"> dbid<\/span> column in <span class=\"STCodeinTextChar\"> sys.sysdatabases<\/span>, as well as to several other system views and DMVs.<\/li>\n<li><span class=\"STCodeinTextBold\"> resource_associated_entity_id<\/span> &#8211; depending upon the <span class=\"STCodeinTextChar\"> resource_type<\/span>, this value is either:\n<ul>\n<li>object_id of the target object, if <span class=\"STCodeinTextChar\"> resource_type = OBJECT<\/span><\/li>\n<li>the <span class=\"STCodeinTextChar\"> object_id<\/span> of the parent object (courtesy of <span class=\"STCodeinTextChar\"> sys.partitions<\/span>), if the <span class=\"STCodeinTextChar\"> resource_type<\/span> is a <span class=\"STCodeinTextChar\"> KEY<\/span>, <span class=\"STCodeinTextChar\"> PAGE<\/span>, or <span class=\"STCodeinTextChar\"> RID<\/span>.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>Returning the name of the object is possible via the <span class=\"STCodeinTextChar\"> OBJECT_ID()<\/span> system function, by passing in both the <span class=\"STCodeinTextChar\"> object_id<\/span> and the <span class=\"STCodeinTextChar\"> database_id<\/span> (respectively).<\/p>\n<p>Of particular interest here is the <span class=\"STCodeinTextChar\"> resource_type<\/span> column, which indentified the<span class=\"STBold\"> lock type<\/span> being requested. SQL Server can lock a number of different types of resource, the most obvious being <span class=\"STBold\">tables (<\/span><span class=\"STCodeinTextChar\">OBJECT<\/span><span class=\"STBold\"> locks)<\/span>, <span class=\"STBold\">pages (<\/span><span class=\"STCodeinTextChar\">PAGE<\/span><span class=\"STBold\"> locks)<\/span>, <span class=\"STBold\">rows (<\/span><span class=\"STCodeinTextChar\">RID<\/span><span class=\"STBold\"> locks)<\/span>, and <b>keys<\/b> (<span class=\"STCodeinTextChar\">KEY<\/span> locks), in order of increasing granularity. Locks are granted and released on these objects as needed, in order to satisfy the requirements of the isolation levels in use by the various sessions. In the locking hierarchy, row and key locks are the lowest level, most granular forms of lock. The more granular the lock, the higher the degree of concurrent access can be supported. However, with that comes a higher memory overhead, from having to manage a large number of individual locks.<\/p>\n<p>SQL Server automatically chooses locks of the highest possible granularity, suitable for the given workload. However, if too many individual locks are being held on an index or heap, or if forced to do so due to memory pressure, SQL Server may use lock escalation to reduce the total number of locks being held. For example, a large number of individual row locks may be escalated to a single table lock, or a number of page locks may be escalated to a table lock (escalation is always to a table lock). While this will result in lower overhead on SQL Server, the cost will be lower concurrency. If processes are running on your servers that are causing lock escalation, it&#8217;s worth investigating whether the escalation is justified, or if SQL tuning can be performed to prevent it. The <span class=\"STCodeinTextChar\"> index_lock_promotion_count<\/span> column of the <span class=\"STCodeinTextChar\"> sys.dm_db_index_operational_stats<\/span> DMV can let you know if lock escalation is occurring frequently on a given index or heap.<\/p>\n<p>Lock escalation<br \/>\n A full discussion of this topic is beyond the scope of this book, but good explanations are offered on <a href=\"http:\/\/technet.microsoft.com\/en-us\/magazine\/2008.04.blocking.aspx\">MSDN<\/a> and by <a href=\"http:\/\/blogs.msdn.com\/b\/sqlserverstorageengine\/archive\/2006\/05\/17\/lock-escalation.aspx\"> Sunil Agarwal<\/a>, Program Manager for the Storage Engine Group.<\/p>\n<p>The remaining columns of the <span class=\"STCodeinTextChar\"> sys.dm_tran_locks<\/span> DMV offer a means to determine the nature, status and owner of the lock request, and to relate this locking information to that available from other DMVs, regarding the sessions or transactions with which the locks are associated.<\/p>\n<ul>\n<li><span class=\"STCodeinTextBold\"> request_mode<\/span> &#8211; type of lock that is being held, or has been requested, such as Shared (<span class=\"STBold\">S<\/span>), Update (<span class=\"STBold\">U<\/span>), Exclusive (<span class=\"STBold\">X<\/span>), Intent Exclusive (<span class=\"STBold\">IX<\/span>), and so on. Please see BOL for full details on the various locking modes.<\/li>\n<li><span class=\"STCodeinTextBold\"> request_status<\/span> &#8211; status of the lock:\n<ul>\n<li><span class=\"STCodeinTextChar\"> GRANT<\/span> -indicates the lock has been taken<\/li>\n<li><span class=\"STCodeinTextChar\"> CONVERT<\/span> &#8211; the request is in the process of being fulfilled<\/li>\n<li><span class=\"STCodeinTextChar\"> WAIT<\/span> &#8211; the resource is not locked, but is trying to lock the resource.<\/li>\n<\/ul>\n<\/li>\n<li><span class=\"STCodeinTextBold\"> request_owner_type<\/span> &#8211; type of owner of the transaction:\n<ul>\n<li><span class=\"STCodeinTextChar\"> TRANSACTION<\/span><\/li>\n<li><span class=\"STCodeinTextChar\"> CURSOR<\/span><\/li>\n<li><span class=\"STCodeinTextChar\"> SESSION<\/span><\/li>\n<li><span class=\"STCodeinTextChar\"> SHARED_TRANSACTION_WORKSPACE<\/span><\/li>\n<li><span class=\"STCodeinTextChar\"> EXCLUSIVE_TRANSACTION_WORKSPACE.<\/span><\/li>\n<\/ul>\n<\/li>\n<li><span class=\"STCodeinTextBold\"> request_session_id<\/span> &#8211; the <span class=\"STCodeinTextChar\"> session_id<\/span> of the requestor. Exposing this column allows the DBA to join back to the information provided in any of the <span class=\"STCodeinTextChar\"> sys.dm_exec_*<\/span> DMVs as well as <span class=\"STCodeinTextChar\"> sys.sysprocesses<\/span> (via a join to its <span class=\"STCodeinTextChar\"> spid<\/span> column).<\/li>\n<li><span class=\"STCodeinTextBold\"> request_owner_id<\/span> &#8211; this column is only valid when the <span class=\"STCodeinTextChar\"> request_owner_type<\/span> is <span class=\"STCodeinTextChar\"> TRANSACTION<\/span>. In that case the value is the <span class=\"STCodeinTextChar\"> transaction_id<\/span> for the associated transaction.<\/li>\n<\/ul>\n<p>One final column that is very useful is the <span class=\"STCodeinTextBold\"> lock_owner_address<\/span>, which is a binary address used internally to track the lock request. It is not interesting to the DBA in its own right, but in the fact that it can be used to join to the <span class=\"STCodeinTextChar\"> resource_address<\/span> column in the <span class=\"STCodeinTextChar\"> sys.dm_os_waiting_tasks<\/span> DMV, to relate locking information to tasks that are waiting for a resource to become available, before proceeding (i.e. are blocked). We&#8217;ll discuss this in much more detail in the section entitled <span class=\"STItalic\">Investigating Blocking<\/span>.<\/p>\n<p class=\"style7\">Other <span class=\"STCodeinTextChar\"> sys.dm_tran_locks<\/span> columns<\/p>\n<p class=\"style8\">This DMV provides other columns that I don&#8217;t often use as a DBA, and so have omitted. For a full column listing, please refer to: <a href=\"http:\/\/technet.microsoft.com\/en-us\/library\/ms190345.aspx\"> <span class=\"STHyperlink\"> http:\/\/technet.microsoft.com\/en-us\/library\/ms190345.aspx<\/span><\/a>.<\/p>\n<h2>Investigating locking<\/h2>\n<p>A single request to query or modify some data may cause multiple locks to be granted, against several resources in the database. For example, consider the <span class=\"STCodeinTextChar\"> UPDATE<\/span> statement below against the <span class=\"STCodeinTextChar\"> Production.ProductCategory<\/span> table in the <span class=\"STCodeinTextChar\"> AdventureWorks<\/span> database, shown in Listing 2.<\/p>\n<pre>\tBEGIN  TRANSACTION \r\n\tUPDATE [Production].[ProductCategory] \r\n\tSET [Name]  = 'Parts' \r\n\tWHERE [Name] = 'Components'; \r\n\t--ROLLBACK TRANSACTION\r\n<\/pre>\n<p class=\"caption\">Listing 2: An uncommitted update of the <span class=\"STCodeinTextChar\"> Production<\/span> table in <span class=\"STCodeinTextChar\"> AdventureWorks<\/span>.<\/p>\n<p>A query against the <span class=\"STCodeinTextChar\"> sys.dm_trans_locks<\/span> DMV, shown in Listing 3, will reveal the locks acquired in the <span class=\"STCodeinTextChar\"> AdventureWorks<\/span> database, as a result of our uncommitted <span class=\"STCodeinTextChar\"> UPDATE<\/span>.<\/p>\n<pre>\tSELECT\u00a0 [resource_type] ,\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 DB_NAME([resource_database_id]) AS [Database Name]  ,\r\n\t\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0CASE  WHEN DTL.resource_type  IN  ( 'DATABASE', 'FILE', 'METADATA' )\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 THEN DTL.resource_type\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 WHEN DTL.resource_type =  'OBJECT'\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 THEN  OBJECT_NAME(DTL.resource_associated_entity_id,\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 DTL.[resource_database_id])\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 WHEN DTL.resource_type IN  ( 'KEY', 'PAGE', 'RID' )\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 THEN  (  SELECT\u00a0 OBJECT_NAME([object_id])\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 FROM\u00a0\u00a0\u00a0 sys.partitions\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 WHERE\u00a0\u00a0 sys.partitions.hobt_id = \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 DTL.resource_associated_entity_id\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 )\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ELSE  'Unidentified'\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 END  AS requested_object_name ,\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 [request_mode] ,\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 [resource_description]\r\n\tFROM\u00a0\u00a0\u00a0 sys.dm_tran_locks DTL\r\n\tWHERE\u00a0\u00a0 DTL.[resource_type] &lt;&gt;  'DATABASE' ;\r\n<\/pre>\n<p class=\"caption\">Listing 3: Locking due to single <span class=\"STCodeinTextChar\"> UPDATE<\/span> statement against a user table in SQL Server.<\/p>\n<p>The query is actually very straightforward, but made to look a little more intimidating by the <span class=\"STCodeinTextChar\"> CASE<\/span> statement, where we return a value for the object name (or parent object name, when dealing with <span class=\"STCodeinTextChar\"> PAGE<\/span> and <span class=\"STCodeinTextChar\"> KEY<\/span> locks, in this case) for the locked resource. The way in which we need to do this depends on the type of lock being held. In the case of <span class=\"STCodeinTextChar\"> DATABASE<\/span>, <span class=\"STCodeinTextChar\"> FILE<\/span>, or <span class=\"STCodeinTextChar\"> METADATA<\/span> locks, the query simply returns the value for the lock type. For <span class=\"STCodeinTextChar\"> OBJECT<\/span> locks, the parent object is directly associated with the <span class=\"STCodeinTextChar\"> object_id<\/span>. For <span class=\"STCodeinTextChar\"> PAGE<\/span>, <span class=\"STCodeinTextChar\"> RID<\/span>, or <span class=\"STCodeinTextChar\"> KEY<\/span> locks, we need to look up the <span class=\"STCodeinTextChar\"> object_id<\/span> associated to the <span class=\"STCodeinTextChar\"> object_id<\/span> in the <span class=\"STCodeinTextChar\"> sys.partitions<\/span> catalog view, by joining the <span class=\"STCodeinTextChar\"> hobt_id<\/span> in that view to the <span class=\"STCodeinTextChar\"> resource_associated_entity_id<\/span> in <span class=\"STCodeinTextChar\"> sys.dm_tran_locks<\/span>. Each session on SQL Server also creates a <span class=\"STCodeinTextChar\"> DATABASE<\/span> shared lock, which I&#8217;ve filtered out in the <span class=\"STCodeinTextChar\"> WHERE<\/span> clause.<\/p>\n<p>The output of the query looks as shown in Figure 2.<\/p>\n<p class=\"illustration\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1151-DMV2.jpg\" alt=\"1151-DMV2.jpg\" \/><\/p>\n<p class=\"caption\">Figure 2: Locking results for the AdventureWorks database<\/p>\n<p>You&#8217;ll note that locks are issued against multiple objects and at different granularities to allow for the update:<\/p>\n<ul>\n<li>an intent-exclusive lock is placed on the <span class=\"STCodeinTextChar\"> ProductCategory<\/span> table denoting the intent to take exclusive locks lower in the hierarchy<\/li>\n<li>two pages (one in each of two indexes) are also granted intent-exclusive locks; the <i>n:x<\/i> notation in the <span class=\"STCodeinTextChar\"> resource_description<\/span> column signifies the <i>nth<\/i> partition and the <i>xth<\/i> page in that partition, for a given <span class=\"STCodeinTextChar\"> PAGE<\/span> lock<\/li>\n<li>three exclusive <span class=\"STCodeinTextChar\"> KEY<\/span> locks are granted against the individual index keys; the values listed in <span class=\"STCodeinTextChar\"> resource_description<\/span> are hashes of the key value.<\/li>\n<\/ul>\n<p>When everyone plays by the rules this architecture works fairly well. However, situations arise when transactions don&#8217;t release locks in a timely manner, due to I\/O bottlenecks on the server, or when locks are held longer than they should be due to poorly coded T-SQL.<\/p>\n<p><b>Reducing lock times<\/b><br \/>\n In this book, we must focus on the DMOs, so a full discussion of strategies for minimizing the length of time that locks are held is out of scope. SQL Server MVP Brad McGehee offers his take on reducing lock times on <a href=\"http:\/\/www.sql-server-performance.com\/2006\/reducing-locks\/\"> SQLServerPerformance.com<\/a>.<\/p>\n<p>If, from a second tab in SSMS, we now run a second query, shown in Listing 4 (having neither committed or rolled back the query in Listing 2), we&#8217;ll introduce some blocking in the <span class=\"STCodeinTextChar\"> AdventureWorks<\/span> database.<\/p>\n<pre>\tSELECT\u00a0 *\r\n\tFROM\u00a0\u00a0\u00a0 [Production].[ProductCategory] ;\r\n<\/pre>\n<p class=\"caption\">Listing 4: A simple query against the <span class=\"STCodeinTextChar\"> ProductCategory<\/span> table, which will be blocked.<\/p>\n<p>We&#8217;ll present a more detailed script for detecting blocking in the next section, but the one in Listing 5 demonstrates the basic pattern for joining to the execution-related DMOs to find out which sessions are involved in blocking, which login &#8220;owns&#8221; these sessions, and what SQL statements they are executing.<\/p>\n<pre>\tSELECT\u00a0 DTL.[request_session_id] AS [session_id]  ,\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 DB_NAME(DTL.[resource_database_id]) AS [Database]  ,\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 DTL.resource_type ,\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 CASE  WHEN DTL.resource_type  IN  ( 'DATABASE', 'FILE', 'METADATA' )\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 THEN DTL.resource_type\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 WHEN DTL.resource_type =  'OBJECT'\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 THEN  OBJECT_NAME(DTL.resource_associated_entity_id,\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 DTL.[resource_database_id])\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 WHEN DTL.resource_type IN  ( 'KEY', 'PAGE', 'RID' )\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 THEN  (  SELECT\u00a0 OBJECT_NAME([object_id])\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 FROM\u00a0\u00a0\u00a0 sys.partitions\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 WHERE\u00a0\u00a0 sys.partitions.hobt_id = \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 DTL.resource_associated_entity_id\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 )\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ELSE  'Unidentified'\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 END  AS [Parent Object]  ,\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 DTL.request_mode AS [Lock Type]  ,\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 DTL.request_status AS [Request Status]  ,\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 DER.[blocking_session_id] ,\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 DES.[login_name]  ,\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 CASE DTL.request_lifetime\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 WHEN 0  THEN DEST_R.TEXT\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ELSE DEST_C.TEXT\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 END  AS [Statement]\r\n\tFROM\u00a0\u00a0\u00a0 sys.dm_tran_locks DTL\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 LEFT  JOIN  sys.[dm_exec_requests] DER\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ON DTL.[request_session_id]  = DER.[session_id]\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 INNER  JOIN  sys.dm_exec_sessions  DES\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0ON DTL.request_session_id  =  DES.[session_id]\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 INNER  JOIN  sys.dm_exec_connections  DEC\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0ON DTL.[request_session_id]  =  DEC.[most_recent_session_id]\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 OUTER  APPLY  sys.dm_exec_sql_text(DEC.[most_recent_sql_handle])\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0AS DEST_C\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 OUTER  APPLY  sys.dm_exec_sql_text(DER.sql_handle) AS DEST_R\r\n\tWHERE\u00a0\u00a0 DTL.[resource_database_id] =  DB_ID()\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 AND DTL.[resource_type] NOT  IN  ( 'DATABASE', 'METADATA' )\r\n\tORDER  BY DTL.[request_session_id]  ;\r\n<\/pre>\n<p class=\"caption\">Listing 5: Which sessions are causing blocking and what statement are they running?<\/p>\n<p class=\"caption\">The result set returned is shown in Figure 3.<\/p>\n<p class=\"illustration\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1151-DMV4.jpg\" alt=\"1151-DMV4.jpg\" \/><\/p>\n<p class=\"illustration\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1151-DMV3.jpg\" alt=\"1151-DMV3.jpg\" \/><\/p>\n<p class=\"caption\">Figure 3: Locking and blocking results in <span class=\"STCodeinTextChar\"> AdventureWorks<\/span>, based upon two sample transactions.<\/p>\n<p class=\"MsoNormal\">The <span class=\"STCodeinTextChar\"> LEFT JOIN<\/span> in Listing 5 is necessary because the request no longer exists for the initial (blocking) <span class=\"STCodeinTextChar\"> UPDATE<\/span> statement; although it is neither committed nor rolled back, execution is complete. Therefore, an <span class=\"STCodeinTextChar\"> INNER JOIN<\/span> would omit those rows in the <span class=\"STCodeinTextChar\"> sys.dm_tran_locks<\/span> view that refer to the updating query. This is also part of the reason why we need two <span class=\"STCodeinTextChar\"> OUTER APPLY<\/span> joins to <span class=\"STCodeinTextChar\"> sys.dm_exec_sql_text<\/span>: one using <span class=\"STCodeinTextChar\"> sql_handle<\/span> from <span class=\"STCodeinTextChar\"> sys.dm_exec_requests<\/span> and one using <span class=\"STCodeinTextChar\"> sys.dm_exec_connections.most_recent_sql_handle<\/span>. Since the request no longer exists for the blocking update, <span class=\"STCodeinTextChar\"> sys.dm_exec_connections<\/span> needs to supply the <span class=\"STCodeinTextChar\"> sql_handle<\/span>. Conversely, however, <span class=\"STCodeinTextChar\"> sys.dm_exec_connections<\/span> (and <span class=\"STCodeinTextChar\"> sys.dm_exec_sessions<\/span>) is only updated with cumulative values for such columns as <span class=\"STCodeinTextChar\"> cpu_time<\/span>, <span class=\"STCodeinTextChar\"> memory_usage<\/span>, and <span class=\"STCodeinTextChar\"> sql_handle<\/span> <span class=\"STItalic\">after<\/span> associated requests complete execution. The blocked query is still executing and so we can&#8217;t rely on <span class=\"STCodeinTextChar\"> SDEC.most_recent_sql_handle<\/span> for the command text of the live request. The acid test for deciding how to call <span class=\"STCodeinTextChar\"> sys.dm_exec_sql_text<\/span> is the <span class=\"STCodeinTextChar\"> request_lifetime<\/span> column in <span class=\"STCodeinTextChar\"> sys.dm_tran_locks<\/span>. If <span class=\"STCodeinTextChar\"> request_lifetime<\/span> is 0 it signifies that the request is still active and <span class=\"STCodeinTextChar\"> sys.dm_exec_requests.sql_handle<\/span> should be used. Otherwise, the value needs to come from <span class=\"STCodeinTextChar\"> sys.dm_exec_connections.most_recent_sql_handle<\/span>.<\/p>\n<p>Blocking analysis using sys.dm_trans_locks and sys.dm_os_waiting_tasks<\/p>\n<p>A certain amount of blocking activity is normal and to be expected in SQL Server. In the course of daily operation, SQL Server will intentionally prevent one process from accessing an object if another process has a lock on it, in order to preserve data integrity and present a consistent view of the data. The DBA always hopes that the lifespan of a request, i.e. the time between the first transaction starting to execute, and the final commit for the last transaction associated with the request, is fleeting; more akin to the digital equivalent of the fruit fly than the tortoise. In such cases, blocking will generally be of the order of milliseconds and users will be unaffected by it.<\/p>\n<p>In systems with many concurrent transactions, some degree of blocking is probably unavoidable, but the situation can be greatly exacerbated by transactions that are longer or more complex than is strictly dictated by the business requirements, or that need to use more restrictive transaction isolation levels (such as <span class=\"STCodeinTextChar\"> REPEATABLE<\/span> <span class=\"STCodeinTextChar\"> READ<\/span> or <span class=\"STCodeinTextChar\"> SERIALIZABLE<\/span>). In my experience as a DBA, the most common causes of these issues are listed below.<\/p>\n<ul>\n<li><b>Poorly written transactions<\/b> &#8211; these include transactions that contain an unnecessarily high number of statements, and transactions that process an unnecessarily large amount of data due to lack of a <span class=\"STCodeinTextChar\"> WHERE<\/span> clause, or predicates that are not as restrictive as they could be. The best query is one that makes as few passes through the data as possible, returns as few rows as is necessary, and only returns the columns required to satisfy the user&#8217;s needs.<\/li>\n<li><b>Poorly designed databases<\/b> &#8211; absent indexing, lack of foreign keys, incorrect or inadequate clustering keys, and poorly chosen data types may all lead to decreased concurrency and excessive blocking.<\/li>\n<li><b>Poorly maintained databases <\/b>&#8211; fragmented indexes and outdated statistics can lead to suboptimal query execution, which causes locks to be held longer than is necessary, and results in table or index scans when a seek should, and could, be used.<\/li>\n<li><b>Poorly designed applications <\/b>&#8211; in terms of crimes against databases committed by application code, I&#8217;ve seen it all: applications that request a batch of records from the database and iterate through the recordset row by row; applications that make almost exclusive use of <span class=\"STCodeinTextChar\"> SELECT *&#8230;<\/span> queries; applications that submit ad hoc SQL code and make no use of stored procedures or other optimized processes. In many cases, particularly when hosting the database for third-party applications, the DBA cannot alter the code and has little influence over getting it fixed.<\/li>\n<\/ul>\n<p>All of this leads to transactions that take longer than necessary to complete, hold locks for longer than necessary, and so cause significant blocking of other transactions.<\/p>\n<p>When investigating blocking issues as a DBA, what we really need is a clear, single page of data that shows the transactions involved in blocking, including the actual SQL statements within those transactions, the sessions to which these transactions belong, and the users who own these sessions.<\/p>\n<p>In order to achieve this, we can start our investigation at the <span class=\"STCodeinTextChar\"> sys.dm_os_waiting_tasks<\/span> DMV. This DMV returns one row for each task that is waiting for a resource to become available before proceeding (i.e. is blocked). Armed with a <span class=\"STCodeinTextChar\"> session_id<\/span> for any tasks that are currently waiting, we can use the <span class=\"STCodeinTextChar\"> resource_address<\/span> column in this DMV to join back to the <span class=\"STCodeinTextChar\"> lock_owner_address<\/span> column in the <span class=\"STCodeinTextChar\"> sys.dm_tran_locks<\/span> DMV, and so relate the waiting tasks information to information on the locks that are being held. Initially, it might seem more logical to join on <span class=\"STCodeinTextChar\"> session_id<\/span> rather than <span class=\"STCodeinTextChar\"> resource_address<\/span>, but remember that the goal is to determine what <span class=\"STItalic\">resource<\/span> contention is occurring; what resource is locked and therefore causing waits for other sessions that need to acquire locks on the object in contention.<\/p>\n<p>From here, we can join to other session\/transaction-related views in order to arrive, ultimately, at a big picture overview of locks that may be causing blocking problems, the sessions and statements that caused those locks to be acquired and those that are blocked as a result. The resulting script, shown in Listing 6, will present both the blocking and blocked requests on the same row, and so provide a very easy way to spot, analyze, and diagnose blocking issues.<\/p>\n<pre>\tUSE [AdventureWorks] ;\r\n\tGO\r\n\tSELECT\u00a0 DTL.[resource_type] AS [resource type]  ,\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 CASE  WHEN DTL.[resource_type]  IN  ( 'DATABASE', 'FILE', 'METADATA' )\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 THEN DTL.[resource_type]\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 WHEN DTL.[resource_type] =  'OBJECT'\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 THEN  OBJECT_NAME(DTL.resource_associated_entity_id)\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 WHEN DTL.[resource_type] IN  ( 'KEY', 'PAGE', 'RID' )\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 THEN  (  SELECT\u00a0 OBJECT_NAME([object_id])\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 FROM\u00a0\u00a0\u00a0 sys.partitions\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 WHERE\u00a0\u00a0 sys.partitions.[hobt_id] = \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 DTL.[resource_associated_entity_id]\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 )\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ELSE  'Unidentified'\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 END  AS [Parent Object]  ,\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 DTL.[request_mode] AS [Lock Type]  ,\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 DTL.[request_status] AS [Request Status]  ,\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 DOWT.[wait_duration_ms] AS [wait duration ms]  ,\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 DOWT.[wait_type] AS [wait type]  ,\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 DOWT.[session_id] AS [blocked session id] ,\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 DES_blocked.[login_name] AS [blocked_user]  ,\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 SUBSTRING(dest_blocked.text, der.statement_start_offset \/ 2,\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ( CASE  WHEN der.statement_end_offset  =  -1\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 THEN  DATALENGTH(dest_blocked.text)\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ELSE der.statement_end_offset\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 END  - der.statement_start_offset )  \/ 2\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0  \u00a0AS [blocked_command] ,\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 DOWT.[blocking_session_id] AS [blocking session id] ,\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 DES_blocking.[login_name] AS [blocking user]  ,\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 DEST_blocking.[text] AS [blocking command]  ,\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 DOWT.resource_description AS [blocking resource detail]\r\n\tFROM\u00a0\u00a0\u00a0 sys.dm_tran_locks DTL\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 INNER  JOIN  sys.dm_os_waiting_tasks DOWT\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ON DTL.lock_owner_address  = DOWT.resource_address\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 INNER  JOIN  sys.[dm_exec_requests] DER\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0ON DOWT.[session_id] = DER.[session_id]\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 INNER  JOIN  sys.dm_exec_sessions DES_blocked\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ON DOWT.[session_id]  = DES_Blocked.[session_id]\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 INNER  JOIN  sys.dm_exec_sessions DES_blocking\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ON DOWT.[blocking_session_id] = DES_Blocking.[session_id]\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 INNER  JOIN  sys.dm_exec_connections  DEC\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ON DTL.[request_session_id]  =  DEC.[most_recent_session_id]\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 CROSS  APPLY  sys.dm_exec_sql_text(DEC.[most_recent_sql_handle])\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0AS DEST_Blocking\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 CROSS  APPLY  sys.dm_exec_sql_text(DER.sql_handle) AS DEST_Blocked\r\n\tWHERE\u00a0\u00a0 DTL.[resource_database_id] =  DB_ID()\r\n<\/pre>\n<p class=\"style6\">Listing 6: Investigating locking and blocking based on waiting tasks<\/p>\n<p class=\"MsoNormal\">To see it in action, we&#8217;ll set up some activity on the <span class=\"STCodeinTextChar\"> Production.Culture<\/span> table of the <span class=\"STCodeinTextChar\"> AdventureWorks<\/span> database. It&#8217;s a narrow table with three columns and two indexes, one clustered index on the primary key and one unique non-clustered index on the <span class=\"STCodeinTextChar\"> Name<\/span> column. Additionally, there is a <span class=\"STCodeinTextChar\"> DEFAULT<\/span> constraint on the <span class=\"STCodeinTextChar\"> ModifiedDate<\/span> column that sets the value to the results of the <span class=\"STCodeinTextChar\"> getdate()<\/span> function.<\/p>\n<p class=\"MsoNormal\">Open a tab in SSMS and execute the query shown in Listing 7. This is our blocking session.<\/p>\n<pre>\tBEGIN  TRANSACTION\r\n\tUPDATE \u00a0Production.Culture\r\n\tSET\u00a0\u00a0\u00a0\u00a0 Name =  'English-British'\r\n\tWHERE\u00a0\u00a0 Name =  'English' ;\r\n\t--ROLLBACK TRANSACTION\r\n<\/pre>\n<p class=\"caption\">Listing 7: An uncommitted <span class=\"STCodeinTextChar\"> UPDATE<\/span> transaction on the <span class=\"STCodeinTextChar\"> Production.Culture<\/span> table<\/p>\n<p>In a separate session, execute the code in Listing 8, to read data from the same table.<\/p>\n<pre>\tSELECT\u00a0 ModifiedDate\r\n\tFROM\u00a0\u00a0\u00a0 Production.Culture\r\n\tWHERE\u00a0\u00a0 Name =  'English' ;\r\n<\/pre>\n<p class=\"caption\">Listing 8: A blocked query against the <span class=\"STCodeinTextChar\"> Production.Culture<\/span> table<\/p>\n<p>Finally, in a third session, <span class=\"STCodeinTextChar\"> INSERT<\/span> a new value into the same table and then read the table.<\/p>\n<pre>\tINSERT\u00a0 INTO Production.Culture\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ( CultureID, Name  )\r\n\tVALUES\u00a0  ( 'jp', 'Japanese' ) ;\r\n\t\r\n\tSELECT\u00a0 *\r\n\tFROM\u00a0\u00a0\u00a0 Production.Culture  ;\r\n<\/pre>\n<p class=\"caption\">Listing 9:An <span class=\"STCodeinTextChar\"> INSERT<\/span> against the <span class=\"STCodeinTextChar\"> Production.Culture<\/span> table<\/p>\n<p>Having executed all three queries, run the DMO script in Listing 6. What we expect to see is that the <span class=\"STCodeinTextChar\"> UPDATE<\/span> query blocks both subsequent <span class=\"STCodeinTextChar\"> SELECT<\/span> queries from the other sessions, but not the <span class=\"STCodeinTextChar\"> INSERT<\/span>, as confirmed by the results shown in Figure 4.<\/p>\n<p class=\"illustration\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1151-DMV5.jpg\" alt=\"1151-DMV5.jpg\" \/><\/p>\n<p class=\"illustration\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1151-DMV6.jpg\" alt=\"1151-DMV6.jpg\" \/><\/p>\n<p class=\"illustration\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1151-DMV7.jpg\" alt=\"1151-DMV7.jpg\" \/><\/p>\n<p class=\"caption\">Figure 4: Blocking in <span class=\"STCodeinTextBold\"> AdventureWorks<\/span>.<\/p>\n<p>This script is one of my favorites; it provides exactly what I need to know. The first half of the results displays information regarding who and what is being blocked, while the second half of the results provides metrics regarding who and what is doing the blocking.<\/p>\n<p>The final column, <span class=\"STCodeinTextChar\"> resource_description<\/span> (aliased in the results as &#8220;blocking resource detail&#8221;) provides a value that concatenates the lock type, object type and id, database id, lock id, lock type, and parent\/associated object id for the lock. Quite a bit of information for a single column wouldn&#8217;t you say? This is a result of the denormalized nature of the information that this column stores. The problem is in parsing the values in it; any universal process to do so is undocumented at this time, and we can only hope that at some point (SQL 11 perhaps) Microsoft makes it easier to parse the information, or normalizes the information by adding columns to <span class=\"STCodeinTextChar\"> sys.dm_os_waiting_tasks<\/span>.<\/p>\n<p>The value of the script really becomes evident when one encounters a multi-session blocking situation. Figure 5 show how it can reveal a real-life blocking chain (one I encountered just today on one of my servers). I&#8217;ve omitted all security and identifier columns for my environment, but the remaining data is real.<\/p>\n<p class=\"illusration\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1151-DMV8.jpg\" alt=\"1151-DMV8.jpg\" \/><\/p>\n<p class=\"caption\">Figure 5: A real-life blocking chain.<\/p>\n<p>Session 65 was blocking session 63, which in turn blocked session 78, which blocked 70, which blocked both 111 and 87. Session 87 blocked session 89, which, in turn, blocked about ten other sessions. My DMO script revealed that session 65 was the root cause of the blocking, and the command text returned for this session revealed that I could safely kill that session. Having done so, and by the time I switched query windows in SSMS to re-execute the DMO query, the results were clean. All blocking had been resolved.<\/p>\n<h1>Analyzing Transactional Activity<\/h1>\n<p>There are three DMVs that allow us to monitor active transactions on our SQL Server instances and, in particular, long-running transactions, as well as those transaction that are causing the transaction log to grow rapidly:<\/p>\n<ul>\n<li><span class=\"STCodeinTextBold\"> sys.dm_tran_session_transactions<\/span> &#8211; is primarily an intermediate view that allows us to join the <span class=\"STCodeinTextChar\"> sys.dm_exec<\/span> DMVs (identified primarily on the <span class=\"STCodeinTextChar\"> session_id<\/span> column) with the other <span class=\"STCodeinTextChar\"> sys.dm_tran_*<\/span> DMVs<\/li>\n<li><span class=\"STCodeinTextBold\"> sys.dm_tran_active_transactions<\/span> &#8211; stores transactional information relating to status, type, and state of each transaction currently initiated but not yet completed, on the SQL instance; it also provides information on distributed transactions though some of the DTC-centric columns have been deprecated in SQL Server 2008<\/li>\n<li><span class=\"STCodeinTextBold\"> sys.dm_tran_database_transactions<\/span> &#8211; stores much of the same information in regard to transaction state, status, and type; but also provides extremely granular transaction log metrics and record count and size metadata.<\/li>\n<\/ul>\n<p class=\"MsoNormal\">Over the coming sections we&#8217;ll very briefly review some of the more interesting columns in these DMVs, and then provide some sample troubleshooting scripts. Please refer to the relevant section of <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/ms178621.aspx\">Books Online<\/a> for a full column description for each DMV.<\/p>\n<h2>Transactional DMOs vs. DBCC OPENTRAN<\/h2>\n<p>Prior to SQL Server 2005, most DBAs used the DBCC OPENTRAN command to return information regarding currently open transactions on their SQL Server instances. I continue to use DBCC OPENTRAN for the purpose for which it was designed: to provide information about the oldest transaction still active in a specific database. This tool is a one-trick pony, however. The scripts we&#8217;re going to use based upon the DMVs provide a wealth of extra information. For more information on the syntax and use of DBCC OPENTRAN please visit <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/ms182792.aspx\"> Microsoft Books Online<\/a>.<\/p>\n<h2>sys.dm_tran_session_transactions<\/h2>\n<p>As stated, this view is used primarily for joining the <span class=\"STCodeinTextChar\"> sys.dm_exec_*<\/span> DMVs to the <span class=\"STCodeinTextChar\"> sys.dm_tran_*<\/span> DMVs we&#8217;re discussing here.<\/p>\n<ul>\n<li><span class=\"STCodeinTextBold\"> session_id<\/span> &#8211; identifies the session to which the transaction belongs. The <span class=\"STCodeinTextChar\"> session_id<\/span> may be the most important column for this particular DMV as it allows us to join transactional metadata with, for example, the execution-related DMOs, as well as the <span class=\"STCodeinTextChar\"> sys.sysprocesses<\/span> system compatibility view (as <span class=\"STCodeinTextBold\"> spid<\/span>).<\/li>\n<li><span class=\"STCodeinTextBold\"> transaction_id<\/span> &#8211; just as session_id allows us to join back to the various sys.dm_exec_ DMVs, so <span class=\"STCodeinTextChar\"> transaction_id<\/span> allows the join to the myriad of sys.dm_tran_ DMVs we discuss in this article. This provides the link between sessions and transactions, from a logical perspective, allowing the DBA to make associations between user sessions and requests and the transactions they run, in terms of their activity, characteristics, and locking behavior.<\/li>\n<\/ul>\n<p>The only other column that we&#8217;ll mention here is is_user_transaction, which denotes whether the transaction is a user-initiated transaction (1) or a system transaction (0). This is particularly valuable when we wish to isolate only user transactions. Other columns allow us to exclude or isolate distributed, enlisted, or bound transactions, as required by our circumstances.<\/p>\n<p>Listing 10 illustrates a very simple query against this DMV, just to illustrate the sort of result set returned for current transactions on the active session.<\/p>\n<pre>\tBEGIN  TRANSACTION \r\n\tSELECT\u00a0 DTST.[session_id] ,\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 DTST.[transaction_id] ,\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 DTST.[is_user_transaction]\r\n\tFROM\u00a0\u00a0\u00a0 sys.[dm_tran_session_transactions] AS DTST\r\n\tWHERE\u00a0\u00a0 DTST.[session_id] =  @@SPID\r\n\tORDER  BY DTST.[transaction_id] \r\n\tCOMMIT\r\n\t\r\n\tsession_id\u00a0 transaction_id\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 is_user_transaction\r\n\t----------- -------------------- -------------------\r\n\t56\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 1550381\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 1\r\n\t\r\n\t(1 row(s) affected)\r\n<\/pre>\n<p class=\"caption\">Listing 10: Basic query against <span class=\"STCodeinTextChar\"> sys.dm_tran_session_transactions<\/span> for transactions on the current session.<\/p>\n<h2>sys.dm_tran_active_transactions<\/h2>\n<p>The <span class=\"STCodeinTextChar\"> sys.dm_tran_active_transactions<\/span> DMV returns a list of all transactions that are active at the time the query is executed. This view operates at the scope of the entire SQL instance, meaning that results are returned for all databases on the instance. Since this view provides a point-in-time snapshot of currently active transactions, the results will change each time the query is executed, depending upon the state of the individual transactions.<\/p>\n<p class=\"MsoNormal\">Identification columns returned by this view are <span class=\"STCodeinTextChar\"> transaction_id<\/span>, which uniquely identifies a transaction across the entire SQL Server instance, and <span class=\"STCodeinTextChar\"> name<\/span>, which is the name of the transaction. I consider it a good habit, if not a best practice, to name transactions, as the following code snippet demonstrates:<\/p>\n<pre>\tBEGIN TRANSACTION Trans_Name \r\n--Some SQL Code goes here \r\nCOMMIT TRANSACTION Trans_Name\r\n<\/pre>\n<p>In this example, <span class=\"STCodeinTextChar\"> Trans_Name<\/span> will be returned as the value for the <span class=\"STCodeinTextChar\"> name<\/span> column for this record in <span class=\"STCodeinTextChar\"> sys.dm_tran_active_transactions<\/span>, assuming the transaction is still active when you issue a query against this DMV. If the transaction is not explicitly named, this column will be populated by values such as <span class=\"STCodeinTextChar\"> SELECT<\/span>, <span class=\"STCodeinTextChar\"> INSERT<\/span>, <span class=\"STCodeinTextChar\"> UPDATE<\/span>, <span class=\"STCodeinTextChar\"> DELETE<\/span> in the case of those Data Modification Language (DML) transactions. You&#8217;ll also see <span class=\"STCodeinTextChar\"> Worktable<\/span> as a value when returning results from <span class=\"STCodeinTextChar\"> tempdb<\/span> for the hashing and temp\/intermediate transactional processing that occurs there. Finally, you will see the value of <span class=\"STCodeinTextChar\"> DTCXACT<\/span> for unnamed distributed transactions.<\/p>\n<p>This DMV also returns the time that the transaction was started (<span class=\"STCodeinTextChar\">transaction_begin_time<\/span>), as well as columns that indentify the type (<span class=\"STCodeinTextChar\">transaction_type<\/span>) and state (<span class=\"STCodeinTextChar\">transaction_state<\/span>, or <span class=\"STCodeinTextChar\"> dtc_state<\/span> for distributed transactions). These type and state columns return integer values, the meanings of which are deciphered in the sample query shown in Listing 11.<\/p>\n<pre>\tSELECT\u00a0 DTAT.transaction_id ,\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 DTAT.[name]  ,\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 DTAT.transaction_begin_time ,\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 CASE DTAT.transaction_type\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 WHEN 1  THEN  'Read\/write'\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 WHEN 2  THEN  'Read-only'\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 WHEN 3  THEN  'System'\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 WHEN 4  THEN  'Distributed'\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 END  AS transaction_type  ,\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 CASE DTAT.transaction_state\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 WHEN 0  THEN 'Not fully initialized'\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 WHEN 1  THEN  'Initialized, not started'\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 WHEN 2  THEN  'Active'\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 WHEN 3  THEN  'Ended' -- only applies to read-only transactions\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 WHEN 4  THEN  'Commit initiated'-- distributed transactions only\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 WHEN 5  THEN  'Prepared, awaiting resolution'  \r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 WHEN 6  THEN  'Committed'\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 WHEN 7  THEN  'Rolling back'\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 WHEN 8  THEN  'Rolled back'\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 END  AS transaction_state  ,\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 CASE DTAT.dtc_state\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 WHEN 1  THEN  'Active'\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 WHEN 2  THEN  'Prepared'\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 WHEN 3  THEN  'Committed'\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 WHEN 4  THEN  'Aborted'\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 WHEN 5  THEN  'Recovered'\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 END  AS dtc_state\r\n\tFROM\u00a0\u00a0\u00a0 sys.dm_tran_active_transactions DTAT\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 INNER  JOIN  sys.dm_tran_session_transactions DTST\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0ON DTAT.transaction_id = DTST.transaction_id\r\n\tWHERE\u00a0\u00a0 [DTST].[is_user_transaction] = 1\r\n\tORDER  BY DTAT.transaction_begin_time \r\n<\/pre>\n<p class=\"caption\">Listing 11: Querying sys.dm_db_tran_active_transactions.<\/p>\n<p>Notice that we are able to isolate user transactions via a join back to <span class=\"STCodeinTextChar\"> sys.dm_tran_session_transactions<\/span>, filtering on the <span class=\"STCodeinTextChar\"> is_user_transaction<\/span> column. The results of this query are shown in Figure 6.<\/p>\n<p class=\"illustration\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1151-DMV9.jpg\" alt=\"1151-DMV9.jpg\" \/><\/p>\n<p class=\"caption\">Figure 6: Currently active user transactions.<\/p>\n<p>Had we not filtered out transactions associated with system sessions, we&#8217;d also have see results for hash work being performed in <span class=\"STCodeinTextChar\"> tempdb<\/span>, similar to the record shown in Figure 7.<\/p>\n<p class=\"illustration\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1151-DMV10.jpg\" alt=\"1151-DMV10.jpg\" \/><\/p>\n<p class=\"caption\">Figure 7: Worktable transaction records in sys.dm_db_tran_active_transactions.<\/p>\n<p>Queries against <span class=\"STCodeinTextChar\"> sys.dm_tran_active_transactions<\/span> will often return a great number of results that look like this, since these worktables are created and destroyed all the time on our SQL instances as a result of sorting and hashing intermediate results sets before returning them to an end-user.<\/p>\n<p>An alternative way to eliminate the worktables would be to add a predicate to the name column, as the code snippet in Listing 12 demonstrates.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\t...\r\n\tFROM sys.dm_tran_active_transactions DTAT \r\nWHERE DTAT.name  &lt;&gt; 'worktable'  \r\nORDER BY DTAT.transaction_begin_time\r\n<\/pre>\n<p class=\"caption\">Listing 12: Eliminating worktables from the results returned by <span class=\"STCodeinTextChar\"> active_transactions<\/span>.<\/p>\n<p class=\"MsoNormal\">While information regarding the transaction start time, type and the current state of the transaction may be valuable, it is rare to query this DMV in isolation, as it provides an incomplete picture of transactional activity. We cannot tell, for example, the databases on which these transactions are running, the sessions or users that are running them, or the statements that comprise each transaction. To retrieve this information, we&#8217;ll need to join DMVs via the <span class=\"STCodeinTextChar\"> transaction_id<\/span> column, as will be demonstrated shortly.<\/p>\n<h2>sys.dm_tran_database_transactions<\/h2>\n<p>The <span class=\"STCodeinTextChar\"> sys.dm_tran_database_transactions<\/span> DMV is server-scoped (the name is deceptive) and provides detailed information about the transactions occurring on your SQL Server instance. Like the <span class=\"STCodeinTextChar\"> sys.dm_db_tran_active_transactions<\/span> view, it provides point-in-time snapshot data, so the results may vary each time the view is queried. A cursory glance at the available columns may lead one to assume that the data retuned will be similar to that provided by the <span class=\"STCodeinTextChar\"> sys.dm_tran_active_transactions<\/span> view. In fact, however, <span class=\"STCodeinTextChar\"> sys.dm_tran_database_transactions<\/span> provides a much more granular level of detail about each transaction and gives &#8220;physical presence&#8221; to a transaction, in that it returns information about how it is using the transaction log file.<\/p>\n<p>Alongside a <span class=\"STCodeinTextChar\"> transaction_id<\/span> column, the DMV exposes a <span class=\"STCodeinTextChar\"> database_id<\/span> column, which identifies the database to which the transaction is associated. Occasionally, for example when isolating issues associated with just those databases using a particular isolation level or recovery model, it is useful to use this column to join to the <span class=\"STCodeinTextChar\"> sys.database<\/span> system catalog view in order to return such columns as the <span class=\"STCodeinTextChar\"> snapshot_isolation_state<\/span>, <span class=\"STCodeinTextChar\"> recovery_model<\/span>, and so on. The <span class=\"STCodeinTextChar\"> database_id<\/span> column can also be used as the sole parameter of the <span class=\"STCodeinTextChar\"> DB_NAME()<\/span> function, in order to return the name of the database.<\/p>\n<p>Like the <span class=\"STCodeinTextChar\"> active_transactions<\/span> DMV, the <span class=\"STCodeinTextChar\"> database_transactions<\/span> DMV also exposes columns describing the type (<span class=\"STCodeinTextChar\">database_transaction_type<\/span>) and state (<span class=\"STCodeinTextChar\">database_transaction_state<\/span>) of a transaction. The integer values returned must be &#8220;decoded&#8221; in our queries, as demonstrated for the state column by the code snippet in Listing 13.<\/p>\n<pre>\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 CASE SDTDT.database_transaction_state\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 WHEN 1  THEN 'Not initialized'\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 WHEN 3  THEN  'initialized, but not producing log records'\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 WHEN 4  THEN  'Producing log records'\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 WHEN 5  THEN  'Prepared'\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 WHEN 10  THEN  'Committed'\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 WHEN 11  THEN  'Rolled back'\r\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 WHEN 12  THEN  'Commit in process'\r\n<\/pre>\n<p class=\"caption\">Listing 13: Decoding the integer values returned by <span class=\"STCodeinTextChar\"> database_transaction_state<\/span>.<\/p>\n<p>Below is a list of some of the other important columns available through <span class=\"STCodeinTextChar\"> sys.dm_tran_database_transactions<\/span> that allow us to investigate long-running transactions, as well as use and abuse of the transaction log.<\/p>\n<ul>\n<li><span class=\"STCodeinTextBold\"> database_transaction_begin_time<\/span> &#8211; time at which the transaction began producing log records. Note that this may not be the same as the time when the transaction was initiated, as there may be a delay between a transaction being initiated and it starting processing, if required resources are locked or if there is a wait for server resources such as CPU.<\/li>\n<li><span class=\"STCodeinTextBold\"> database_transaction_log_record_count<\/span> &#8211; number of log records for the transaction at the time the query executed. Note that, if the value of <span class=\"STCodeinTextChar\"> database_transaction_state<\/span> is 5 or less, the transaction will still be producing log records, so the value of <span class=\"STCodeinTextChar\"> database_transaction_log_record_count<\/span> will not be equal to the total number of records that will be generated by the transaction.<\/li>\n<li><span class=\"STCodeinTextBold\"> database_transaction_log_bytes_used<\/span> &#8211; amount of bytes this transaction currently contributes to the total used in the transaction log for the database.<\/li>\n<li><span class=\"STCodeinTextBold\"> database_transaction_log_bytes_reserved<\/span> &#8211; bytes reserved in the log for this transaction.<\/li>\n<li><span class=\"STCodeinTextBold\"> database_transaction_begin_lsn<\/span>, <span class=\"STCodeinTextBold\"> database_transaction_last_lsn<\/span> &#8211; Log Sequence Number (LSN) of the first and last records in the log for this transaction.<\/li>\n<li><span class=\"STCodeinTextBold\"> database_transaction_most_recent_savepoint_lsn<\/span> &#8211; if savepoints are used, this is the most recent one that can be rolled back to. <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/ms181299.aspx\">Savepoints<\/a> are not discussed further in this article.<\/li>\n<li><span class=\"STCodeinTextBold\"> database_transaction_commit_lsn<\/span> &#8211; the LSN that recorded the commit for the transaction.<\/li>\n<li><span class=\"STCodeinTextBold\"> database_transaction_last_rollback_lsn<\/span> &#8211; if a rollback has occurred, this is the most recent LSN that the transaction was rolled back to. If there was no rollback, the value will be the last LSN recorded in the log.<\/li>\n<li><span class=\"STCodeinTextBold\"> database_transaction_next_undo_lsn<\/span> &#8211; during the undo portion of a recovery, this will be the LSN to be rolled back (undone).<\/li>\n<\/ul>\n<h2>Assessing transaction log impact<\/h2>\n<p>The sys.dm_tran_database_transactions DMV is the only one that provides insight into the effects of user activity on the database transaction logs. Using this DMV, and joining across to other transaction-related and execution-related DMVs, as described previously, we can develop a query, shown in Listing 14, which will identify all active transactions and their physical effect on the databases&#8217; transaction logs. This is especially useful when seeking out transactions that may be causing explosive transaction log growth.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\tSELECT DTST.[session_id], \r\n\u00a0DES.[login_name] AS [Login Name], \r\n\u00a0DB_NAME (DTDT.database_id)  AS [Database], \r\n\u00a0DTDT.[database_transaction_begin_time] AS [Begin Time], \r\n\u00a0--  DATEDIFF(ms,DTDT.[database_transaction_begin_time],  GETDATE()) AS [Duration ms], \r\n\u00a0CASE DTAT.transaction_type \r\n\u00a0\u00a0 WHEN 1  THEN 'Read\/write' \r\n\u00a0\u00a0\u00a0  WHEN 2  THEN  'Read-only' \r\n\u00a0\u00a0\u00a0  WHEN 3  THEN  'System' \r\n\u00a0\u00a0\u00a0  WHEN 4  THEN  'Distributed' \r\n\u00a0  END AS [Transaction Type], \r\n\u00a0  CASE DTAT.transaction_state \r\n\u00a0\u00a0\u00a0 WHEN 0  THEN 'Not fully initialized'  \r\n\u00a0\u00a0\u00a0  WHEN 1  THEN  'Initialized, not started' \r\n\u00a0\u00a0\u00a0  WHEN 2  THEN  'Active' \r\n\u00a0\u00a0\u00a0  WHEN 3  THEN  'Ended'  \r\n\u00a0\u00a0\u00a0  WHEN 4  THEN  'Commit initiated'  \r\n\u00a0\u00a0\u00a0  WHEN 5  THEN  'Prepared, awaiting resolution' \r\n\u00a0\u00a0\u00a0  WHEN 6  THEN  'Committed' \r\n\u00a0\u00a0\u00a0  WHEN 7  THEN  'Rolling back' \r\n\u00a0\u00a0\u00a0  WHEN 8  THEN  'Rolled back' \r\n\u00a0  END AS [Transaction State], \r\n\u00a0DTDT.[database_transaction_log_record_count] AS [Log Records], \r\n\u00a0DTDT.[database_transaction_log_bytes_used] AS [Log Bytes Used], \r\n\u00a0DTDT.[database_transaction_log_bytes_reserved] AS [Log Bytes RSVPd], \r\n\u00a0DEST.[text] AS [Last Transaction Text], \r\n \u00a0DEQP.[query_plan] AS [Last Query Plan] \r\nFROM sys.dm_tran_database_transactions DTDT  \r\n\u00a0INNER JOIN sys.dm_tran_session_transactions DTST  \r\n\u00a0\u00a0 ON DTST.[transaction_id] = DTDT.[transaction_id] \r\n\u00a0INNER JOIN sys.[dm_tran_active_transactions] DTAT  \r\n\u00a0\u00a0 ON DTST.[transaction_id] = DTAT.[transaction_id] \r\n\u00a0INNER JOIN sys.[dm_exec_sessions] DES \r\n\u00a0\u00a0  ON DES.[session_id]  = DTST.[session_id]  \r\n\u00a0INNER JOIN sys.dm_exec_connections DEC  \r\n\u00a0\u00a0 ON DEC.[session_id]  = DTST.[session_id]  \r\n\u00a0LEFT  JOIN sys.dm_exec_requests DER \r\n\u00a0\u00a0  ON DER.[session_id]  = DTST.[session_id]  \r\n\u00a0CROSS APPLY sys.dm_exec_sql_text  (DEC.[most_recent_sql_handle])  AS DEST \r\n\u00a0OUTER APPLY sys.dm_exec_query_plan  (DER.[plan_handle])  AS DEQP \r\nORDER BY DTDT.[database_transaction_log_bytes_used] DESC;\r\n\t-- ORDER BY [Duration ms]  DESC;\r\n<\/pre>\n<p class=\"caption\">Listing 14: Transaction log impact of active transactions.<\/p>\n<p>Note the use of OUTER APPLY to join to sys.dm_exec_query_plan. The OUTER APPLY functions as a hybrid of OUTER JOIN and CROSS APPLY in that it will return <span class=\"STCodeinTextChar\"> NULL<\/span> if no value is returned by the function. This prevents the entire row from being excluded from the result set in cases where a plan does not exist for the request (the request may no longer exist in sys.dm_exec_requests by the time Listing 14 is run). Sample output from this Listing is shown in Figure 8.<\/p>\n<p class=\"illustration\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1151-DMV11.jpg\" alt=\"1151-DMV11.jpg\" \/><\/p>\n<p class=\"caption\">Figure 8: Transactions writing heavily to the transaction log.<\/p>\n<p>Simply by un-commenting the calculation of the transaction duration, in Listing 14, and swapping the <span class=\"STCodeinTextChar\"> ORDER BY<\/span> clause, we can investigate the activity of long-running transactions that may be bloating the transaction log file, or preventing it from being truncated.<\/p>\n<h1>Summary<\/h1>\n<p>The <span class=\"STCodeinTextChar\"> sys.dm_tran<\/span>-prefixed Dynamic Management Objects have a broad scope in SQL Server. They not only span the range of DMOs associated with activity at the transactional level of the query engine, but also expose locking and blocking between user sessions, as well as exposing the effects and existence of snapshot isolation in your SQL Server database and the instance in general.<\/p>\n<p>Via queries against <span class=\"STCodeinTextChar\"> sys.dm_tran_locks<\/span>, joining to various <span class=\"STCodeinTextChar\"> sys.dm_exec<\/span>-prefixed DMOs as well as <span class=\"STCodeinTextChar\"> sys.dm_os_waiting_tasks<\/span>, we were able to diagnose locking and blocking occurring within our SQL databases.<\/p>\n<p>Using <span class=\"STCodeinTextChar\"> sys.dm_tran_session_transactions<\/span> we were able to correlate session-based results from <span class=\"STCodeinTextChar\"> sys.dm_exec_connections<\/span>, <span class=\"STCodeinTextChar\"> sys.dm_exec_sessions<\/span>, and <span class=\"STCodeinTextChar\"> sys.dm_exec_requests<\/span> with data from the <span class=\"STCodeinTextChar\"> sys.dm_tran<\/span>-prefixed DMOs. Using <span class=\"STCodeinTextChar\"> sys.dm_tran_active_transactions<\/span> and <span class=\"STCodeinTextChar\"> sys.dm_tran_database_transactions<\/span>, we collected metrics on the duration and status of our users&#8217; transactions, and observed the physical effects of those transactions on the database transaction log files on disk.<\/p>\n<p class=\"note\">This article is adapted from a chapter of &#8216;Performance Tuning with SQL Server Dynamic Management Views&#8217; by Louis Davidson and Tim Ford. To learn more about this book, <a href=\"http:\/\/www.amazon.com\/gp\/product\/1906434476\/\">visit the Amazon page<\/a>.<\/p>\n<\/div>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>There can be a great difference in the performance of a particular routine in a test database, and in a fully loaded production system. When you hit performance problems in a database under load, and there is excessive locking and blocking, how can you determine exactly  where the problems lie, in order to fix them? Read on&#8230;&hellip;<\/p>\n","protected":false},"author":221827,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[143527],"tags":[4168,4170,4750,4933,4150,5282],"coauthors":[50399],"class_list":["post-1001","post","type-post","status-publish","format-standard","hentry","category-database-administration-sql-server","tag-database","tag-database-administration","tag-dynamic-management-views","tag-ebook","tag-sql","tag-tim-ford-and-louis-davidson"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/1001","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/221827"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=1001"}],"version-history":[{"count":8,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/1001\/revisions"}],"predecessor-version":[{"id":74719,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/1001\/revisions\/74719"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=1001"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=1001"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=1001"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=1001"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}