ptg 1374 CHAPTER 37 Locking and Performance could lead to greater page-level contention because the likelihood of the data rows being requested by different processes residing on the same page is greater. Using row-level locking increases the concurrent access to the data. On the other hand, row-level locking consumes more resources (memory and CPU) than page-level locks simply because there is a greater number of rows than pages in a table. If a process needed to access all rows on a page, it would be more efficient to lock the entire page than acquire a lock for each individual row. This would result in a reduction in the number of lock structures in memory that the Lock Manager would have to manage. Which is better—greater concurrency or lower overhead? As shown earlier, in Figure 37.6, it’s a trade-off. As lock size decreases, concurrency improves, but performance degrades due to the extra overhead. As the lock size increases, performance improves due to less overhead, but concurrency degrades. Depending on the application, the database design, and the data, either page-level or row-level locking can be shown to be better than the other in different circumstances. SQL Server makes the determination automatically at runtime—based on the nature of the query, the size of the table, and the estimated number of rows affected—of whether to initially lock rows, pages, or the entire table. In general, SQL Server attempts to first lock at the row level more often than the page level, in an effort to provide the best concur- rency. With the speed of today’s CPUs and the large memory support, the overhead of managing row locks is not as expensive as in the past. However, as the query processes and the actual number of resources locked exceed certain thresholds, SQL Server might attempt to escalate locks from a lower level to a higher level, as appropriate. At times, SQL Server might choose to do both row and page locking for the same query. For example, if a query returns multiple rows, and if enough contiguous keys in a nonclustered index page are selected to satisfy the query, SQL Server might place page locks on the index while using row locks on the data. This reduces the need for lock escalation. Lock Escalation When SQL Server detects that the locks acquired by a query are using too much memory and consuming too many system resources for the Lock Manager to manage the locks effi- ciently, it automatically attempts to escalate row, key, or page locks to table-level locks. For example, because a query on a table continues to acquire row locks and every row in the table will eventually be accessed, it makes sense for SQL Server to escalate the row locks to a table-level lock. After the table-level lock is acquired, the row-level locks are released. This helps reduce locking overhead and keeps the system from running out of available lock structures. Recall from earlier sections in this chapter that the potential need for lock escalation is reflected in the intent locks that are acquired on the table by the process locking at the row or page level. While the default behavior in SQL Server is to escalate to table-level locks, SQL Server 2008 introduces the capability to escalate row or page locks to a single partition via the LOCK_ESCALATION setting in ALTER TABLE. This new option allows you to specify whether escalation is always to the table or partition level. The LOCK_ESCALATION setting can also be used to prevent lock escalation entirely. ptg 1375 SQL Server Lock Granularity 37 NOTE SQL Server never escalates row locks to page locks, only to table or partition-level locks. Also, multiple partition-level locks are never escalated to a single table-level lock. What are the lock escalation thresholds in SQL Server? Currently, SQL Server attempts lock escalation under the following conditions: . Whenever a single T-SQL statement acquires at least 5,000 locks on a single reference of a table, table partition, or index (this value is subject to change in subsequent service packs). Note that lock escalation does not occur if the locks are spread across multiple objects in the same statement—for example, 4,000 locks on one index and 2,500 locks on another. . When the amount of memory required by lock resources exceeds 40% of the avail- able Database Engine memory pool NOTE Generally, if more memory is required for lock resources than is currently available in the Database Engine memory pool, the Database Engine allocates additional memory dynamically to satisfy the request for locks as long as more computer memory is avail- able and the max server memory threshold has not been reached. However, if allocat- ing additional memory would cause paging at the operating system level, more lock space is not allocated. If no more memory is available, or the amount of memory allo- cated to lock resources reaches 60% of the memory acquired by an instance of the Database Engine, further requests for locks generate an out-of-lock memory error. If locks cannot be escalated because of lock conflicts, SQL Server reattempts lock escalation when every 1,250 additional locks are acquired. For example, if another process is also holding locks at the page or row level on the same table (indicated by the presence of that process’s intent lock on the table), lock escalation cannot take place if the lock types are not compatible until the lower-level locks are released by the other processes. In this case, SQL Server continues acquiring locks at the row or page level until the table lock becomes available. Controlling Lock Escalation Escalating locks to the table or partition level can lead to locking contention or blocking for other transactions attempting to access a row or page in the same table. Under certain circumstances, you might want to disable lock escalation. As mentioned previously, lock escalation can be enabled or disabled at the table level using the ALTER TABLE command: ALTER TABLE tablename set (LOCK_ESCALATION ={ AUTO | TABLE | DISABLE } ) ptg 1376 CHAPTER 37 Locking and Performance Setting the option to AUTO allows SQL Server to escalate to the table or partition level. Setting the option to DISABLE prevents escalation to the table or partition level. SQL Server 2008 also supports disabling lock escalation for all tables in all databases within a SQL Server instance using either the 1211 or 1224 trace flags. Trace flag 1211 completely disables lock escalation, regardless of the memory required for lock resources. However, when the amount of memory required for lock resources exceeds 60% of the maximum available Database Engine memory, an out-of-lock memory error is generated. Alternatively, trace flag 1224 disables the built-in lock escalation based on the number of locks acquired, but lock escalation is still possible when the 40% of available Database Engine memory threshold is reached. However, as noted previously, if the locks cannot be escalated, SQL Server could still run out of available memory for locks. NOTE You should be extremely careful when considering disabling lock escalation via the trace flags. A poorly designed application could potentially exhaust the available SQL Server memory with excessive lock structures and seriously degrade SQL Server perfor- mance. It is usually preferable to control lock escalation at the object level via the ALTER TABLE command. Lock Compatibility If a process has already locked a resource, the granting of lock requests by other transac- tions on the same resource is governed by the lock compatibility matrix within SQL Server. Table 37.3 shows the lock compatibility matrix for the locks most commonly acquired by the SQL Server Lock Manager, indicating which lock types are compatible and which lock types are incompatible when requested on the same resource. TABLE 37.3 SQL Server Lock Compatibility Matrix Requested Lock Type Existing Lock Type IS S U IX SIX X Sch-S SCH-M BU Intent shared Yes Yes Yes Yes Yes No Yes No No Shared Yes Yes Yes No No No Yes No No Update Yes Yes No No No No Yes No No Intent exclusive Yes No No Yes No No Yes No No Shared with intent exclusive Yes No No No No No Yes No No Exclusive No No No No No No Yes No No Schema stability Yes Yes Yes Yes Yes Yes Yes No Yes Schema modify No No No No No No No No No Bulk update No No No No No No Yes No Yes ptg 1377 Locking Contention and Deadlocks 37 For example, if a transaction has acquired a shared lock on a resource, the possible lock types that can be acquired on the resource by other transactions are intent shared, shared, update, and schema stability locks. Intent exclusive, SIX, exclusive, schema modification, and bulk update locks are incompatible with a shared lock and cannot be acquired on the resource until the shared lock is released. Locking Contention and Deadlocks In the grand scheme of things, the most likely culprits of SQL Server application perfor- mance problems are typically poorly written queries, poor database and index design, and locking contention. Whereas the first two problems result in poor application perfor- mance, regardless of the number of users on the system, locking contention becomes more of a performance problem as the number of users increases. It is further compounded by increasingly complex or long-running transactions. Locking contention occurs when a transaction requests a lock type on a resource that is incompatible with an existing lock type on the resource. By default, the process waits indefinitely for the lock resource to become available. Locking contention is noticed in the client application through the apparent lack of response from SQL Server. Figure 37.9 demonstrates an example of locking contention. Process 1 has initiated a transaction and acquired an exclusive lock on page 1:325. Before Process 1 can acquire the lock that it needs on page 1:341 to complete its transaction, Process 2 acquires an exclu- sive lock on page 1:341. Until Process 2 commits or rolls back its transaction and releases the lock on Page 1:341, the lock continues to be held. Because this is not a deadlock scenario (which is covered in the “Deadlocks” section, later in this chapter), by default, SQL Server takes no action. Process 1 simply waits indefinitely. Process 1 Process 2 Page 1:325 Page 1:341 Locks Requests Locks 1 23 X X Lock Wait X FIGURE 37.9 Locking contention between two processes. ptg 1378 CHAPTER 37 Locking and Performance Identifying Locking Contention When a client application appears to freeze after submitting a query, this is often due to locking contention. To identify locking contention between processes, you can use the SSMS Activity Monitor, as discussed earlier in this chapter, in the “Monitoring Lock Activity in SQL Server” section; use the sp_who2 stored procedure; or query the sys.dm_tran_locks system catalog view. Figure 37.10 shows an example of a blocking lock as viewed in the SSMS Activity Monitor. To identify whether a process is being blocked using sp_who2, examine the BlkBy column. If any value besides ‘-’ is displayed, it is the SPID of the process that is holding the block- ing lock. In the following output of sp_who2 (edited for space), you can see that process 57 is SUSPENDED, waiting on a lock held by process 53: exec sp_who2 go SPID Status Login HostName BlkBy DBName Command *** info for internal processes deleted *** 51 sleeping rrankins LATITUDED830-W7 . master AWAITING COMMAND 52 sleeping SQLADMIN LATITUDED830-W7 . msdb AWAITING COMMAND 53 sleeping rrankins LATITUDED830-W7 . bigpubs2008 AWAITING COMMAND 54 sleeping rrankins LATITUDED830-W7 . tempdb AWAITING COMMAND FIGURE 37.10 Examining locking contention between two processes in SSMS Activity Monitor. ptg 1379 Locking Contention and Deadlocks 37 55 sleeping rrankins LATITUDED830-W7 . master AWAITING COMMAND 56 sleeping SQLADMIN LATITUDED830-W7 . msdb AWAITING COMMAND 57 SUSPENDED rrankins LATITUDED830-W7 53 bigpubs2008 INSERT 58 sleeping rrankins LATITUDED830-W7 . bigpubs2008 AWAITING COMMAND 59 RUNNABLE rrankins LATITUDED830-W7 . master SELECT INTO To determine what table, page, or rows are involved in blocking and at what level the blocking is occurring, you can query the sys.dm_tran_locks catalog view, as shown in Listing 37.5. LISTING 37.5 Viewing Locking Contention by Using the sys.dm_tran_locks View use bigpubs2008 go select str(request_session_id, 4,0) as spid, convert (varchar(12), db_name(resource_database_id)) As db_name, case when resource_database_id = db_id() and resource_type = ‘OBJECT’ then convert(char(12), object_name(resource_Associated_Entity_id)) else convert(char(16), resource_Associated_Entity_id) end as object, convert(varchar(12), resource_type) as resource_type, convert(varchar(8), request_mode) as mode, convert(varchar(14), resource_description) as resource_desc, convert(varchar(6), request_status) as status from sys.dm_tran_locks order by request_session_id, 3 desc go spid db_name object resource_type mode resource_desc status 52 msdb 0 DATABASE S GRANT 53 bigpubs2008 673416192655360 PAGE IX 1:608 GRANT 53 bigpubs2008 673416192655360 KEY X (928195c101b1) GRANT 53 bigpubs2008 391941215944704 KEY X (59d1a826552c) GRANT 53 bigpubs2008 391941215944704 PAGE IX 1:280 GRANT 53 bigpubs2008 stores OBJECT IX GRANT 53 bigpubs2008 0 DATABASE S GRANT 56 msdb 0 DATABASE S GRANT 57 bigpubs2008 391941215944704 PAGE IS 1:280 GRANT 57 bigpubs2008 391941215944704 KEY S (59d1a826552c) WAIT 57 bigpubs2008 stores OBJECT IS GRANT 57 bigpubs2008 0 DATABASE S GRANT From this output, you can see that Process 57 is waiting for a shared (S) lock on key 59d1a826552c of page 1:280 of the stores table. Process 53 has an intent exclusive (IX) ptg 1380 CHAPTER 37 Locking and Performance lock on that page because it has an exclusive ( X) lock on a key on that page. (Both have the same resource_Associated_Entity_id of 59d1a826552c.) As an alternative to sp_who and the sys.dm_tran_locks view, you can also get specific information on any blocked processes by querying the sys.dm_os_waiting_tasks system catalog view, as shown in Listing 37.6. LISTING 37.6 Viewing Blocked Processes by Using the sys.dm_os_waiting_tasks View select convert(char(4), session_id) as spid, convert(char(8), wait_duration_ms) as duration, convert(char(8), wait_type) as wait_type, convert(char(3), blocking_session_id) as blk, resource_description from sys.dm_os_waiting_tasks where blocking_session_id is not null go spid duration wait_type blk resource_description 57 134118 LCK_M_S 53 keylock hobtid=391941215944704 dbid=8 id=lockfd43800 mode=X associatedObjectId=391941215944704 Setting the Lock Timeout Interval If you do not want a process to wait indefinitely for a lock to become available, SQL Server allows you to set a lock timeout interval by using the SET LOCK_TIMEOUT command. You specify the timeout interval in milliseconds. For example, if you want your processes to wait only 5 seconds (that is, 5,000 milliseconds) for a lock to become available, you execute the following command in the session: SET LOCK_TIMEOUT 5000 If your process requests a lock resource that cannot be granted within 5 seconds, the state- ment is aborted, and you get the following error message: Server: Msg 1222, Level 16, State 52, Line 1 Lock request time out period exceeded. To examine the current LOCK_TIMEOUT setting, you can query the system function @@lock_timeout: select @@lock_timeout go 5000 ptg 1381 Locking Contention and Deadlocks 37 If you want processes to abort immediately if the lock cannot be granted (in other words, no waiting at all), you set the timeout interval to 0. If you want to set the timeout interval back to infinity, execute the SET_LOCK_TIMEOUT command and specify a timeout interval of -1. Minimizing Locking Contention Although setting the lock timeout prevents a process from waiting indefinitely for a lock request to be granted, it doesn’t address the cause of the locking contention. In an effort to maximize concurrency and application performance, you should minimize locking contention between processes as much as possible. Some general guidelines to follow to minimize locking contention include the following: . Keep transactions as short and concise as possible. The shorter the period of time locks are held, the less chance for lock contention. Keep commands that are not essential to the unit of work being managed by the transaction (for example, assign- ment selects, retrieval of updated or inserted rows) outside the transaction. . Keep statements that comprise a transaction in a single batch to eliminate unneces- sary delays caused by network input/output (I/O) between the initial BEGIN TRAN statement and the subsequent COMMIT TRAN commands. . Consider coding transactions entirely within stored procedures. Stored procedures typically run faster than commands executed from a batch. In addition, because they are server resident, stored procedures reduce the amount of network I/O that occurs during execution of the transaction, resulting in faster completion of the transaction. . Commit updates in cursors frequently and as soon as possible. Cursor processing is much slower than set-oriented processing and causes locks to be held longer. NOTE Even though cursors might run more slowly than set-oriented processing, cursors can sometimes be used to minimize locking contention for updates and deletions of a large number of rows from a table, which might result in a table lock being acquired. The UPDATE or DELETE statement itself might complete faster; however, if it is running with an exclusive lock on the table, then no other process can access the table until it com- pletes. By using a cursor to update a large number of rows one row at a time and com- mitting the changes frequently, the cursor uses page- or row-level locks rather than a table-level lock. It might take longer for the cursor to complete the actual update or delete, but while the cursor is running, other processes are still able to access other rows or pages in the table that the cursor doesn’t currently have locked. . Use the lowest level of locking isolation required by each process. For example, if dirty reads are acceptable and accurate results are not imperative, consider using transaction Isolation Level 0. Use the Repeatable Read or Serializable Read isolation levels only if absolutely necessary. ptg 1382 CHAPTER 37 Locking and Performance . Never allow user interaction between a BEGIN TRAN statement and a COMMIT TRAN statement because doing so may cause locks to be held for an indefinite period of time. If a process needs to return rows for user interaction and then update one or more rows, consider using optimistic locking or Snapshot Isolation in your applica- tion. (Optimistic locking is covered in the “Optimistic Locking” section, later in this chapter.) . Minimize “hot spots” in a table. Hot spots occur when the majority of the update activity on a table occurs within a small number of pages. For example, hot spots occur for concurrent insertions to the last page of a heap table or the last pages of a table with a clustered index on a sequential key. You can often eliminate hot spots by creating a clustered index in a table on a column or columns to order the rows in the table in such a way that insert and update activity is spread out more evenly across the pages in the table. Deadlocks A deadlock occurs when two processes are each waiting for a locked resource that the other process currently holds. Neither process can move forward until it receives the requested lock on the resource, and neither process can release the lock it is currently holding until it can receive the requested lock. Essentially, neither process can move forward until the other one completes, and neither one can complete until it can move forward. Two primary types of deadlocks can occur in SQL Server: . Cycle deadlocks—A cycle deadlock occurs when two processes acquire locks on different resources, and then each needs to acquire a lock on the resource that the other process has. Figure 37.11 demonstrates an example of a cycle deadlock. In Figure 37.11, Process 1 acquires an exclusive lock on page 1:201 in a transaction. At the same time, Process 2 acquires an exclusive lock on page 1:301 in a transac- Process 1 Process 2 Page 1:201 Page 1:301 Locks Requests Requests Locks 1 2 4 3 Lock Wait X X X Lock Wait X FIGURE 37.11 An example of a cycle deadlock. ptg 1383 Locking Contention and Deadlocks 37 tion. Process 1 then attempts to acquire a lock on page 1:301 and begins waiting for the lock to become available. Simultaneously, Process 2 requests an exclusive lock on page 1:201, and a deadlock, or “deadly embrace,” occurs. . Conversion deadlocks—A conversion deadlock occurs when two or more processes each hold a shared lock on the same resource within a transaction and each wants to promote the shared lock to an exclusive lock, but neither can do so until the other releases the shared lock. An example of a conversion deadlock is shown in Figure 37.12. It is often assumed that deadlocks happen at the data page or data row level. In fact, dead- locks often occur at the index page or index key level. Figure 37.13 depicts a scenario in which a deadlock occurs due to contention at the index key level. SQL Server automatically detects when a deadlock situation occurs. A separate process in SQL Server, called LOCK_MONITOR, checks the system for deadlocks roughly every 5 seconds. In the first pass, this process detects all the processes that are waiting on a lock resource. The LOCK_MONITOR thread checks for deadlocks by examining the list of waiting lock requests to see if any circular lock requests exist between the processes holding locks and the processes waiting for locks. When the LOCK_MONITOR detects a deadlock, SQL Server aborts the transaction of one of the involved processes. How does SQL Server determine which process to abort? It attempts to choose as the deadlock victim the transaction that it estimates would be least expensive to roll back. If both processes involved in the dead- lock have the same rollback cost and the same deadlock priority, the deadlock victim is chosen randomly. Process 1 Process 2 Page 1:201 Locks Requests Locks Locks 1 4 2 3 Lock Wait XX S S FIGURE 37.12 An example of a conversion deadlock. . table-level lock. What are the lock escalation thresholds in SQL Server? Currently, SQL Server attempts lock escalation under the following conditions: . Whenever a single T -SQL statement acquires at least 5,000 locks. option to AUTO allows SQL Server to escalate to the table or partition level. Setting the option to DISABLE prevents escalation to the table or partition level. SQL Server 2008 also supports disabling. flags. A poorly designed application could potentially exhaust the available SQL Server memory with excessive lock structures and seriously degrade SQL Server perfor- mance. It is usually preferable