5 – DBA as detective 135 Figure 5.9: Number of locks from Bad Query. You can see that there are many locks acquired, mostly exclusive locks at the row level, as indicated by the mode "X" and the type "RID". When I see one SPID that has acquired this number of locks, especially exclusive locks, I get very concerned that something is definitely not as it should be. Often, a simple count of the locks and, more importantly, the types of locks for a specific SPID, is enough to help me locate a poorly performing query, even if there is no obvious blocking. Acquiring locks, just like acquiring connections, requires memory resources and even shared locks, which may not block others from accessing data, can sometimes have a major performance impact due to memory or other resource pressures. 5 – DBA as detective 136 Automating discovery of problems Up to this point we have used sp_who2 to seek out SPIDs that are causing blocking issues, DBCC INPUTBUFFER to elicit the SQL being executed by such a blocking SPID, and then sp_lock to discover some information about the locks being acquired by the offending process. All of this took quite a bit of time to manually discover and resolve, and when a query is locking out an entire table, and depleting any number of other precious resources, this is time you don't necessarily have. What is missing is a single query that will tell all in a single execution. Faced with this pressing need, I have developed just such a query. It returns all of the previously discovered information, and more, in an easily-digestible format, While sp_who2 gives good "at a glance" information, my query dives into the underlying system table, called sysprocesses, in order to retrieve some additional information regarding the blocking and blocked processes. With sp_lock, the underlying system table is syslockinfo. This table does not display intuitive information in the manner of sysprocesses. Specifically, the type of locks have to be identified,via a join to the spt_values table in the Master database. When developing the query, I found it much easier to create a table to store the output of sp_lock and then do a simple count of lock types per SPID. TIP The stored procedure, sp_helptext, is one of those "hidden gems" that I have used many times over the years. When passed any object, such as a view or stored procedure, it will display the code that makes up that object. Running sp_lock through sp_helptext will show the join to the spt_values table. Listing 5.3 shows the query that will, in one fell swoop, find and report on blocked and blocking processes and the number of locks that they are holding. First it creates a temp table to store the output of sp_lock and then it lists all locked and blocked processes, along with the query that each process is currently executing, or that is waiting on resources before it can be executed. SET NOCOUNT ON GO Count the locks IF EXISTS ( SELECT Name FROM tempdb sysobjects WHERE name LIKE '#Hold_sp_lock%' ) 5 – DBA as detective 137 If So Drop it DROP TABLE #Hold_sp_lock GO CREATE TABLE #Hold_sp_lock ( spid INT, dbid INT, ObjId INT, IndId SMALLINT, Type VARCHAR(20), Resource VARCHAR(50), Mode VARCHAR(20), Status VARCHAR(20) ) INSERT INTO #Hold_sp_lock EXEC sp_lock SELECT COUNT(spid) AS lock_count, SPID, Type, Cast(DB_NAME(DBID) as varchar(30)) as DBName, mode FROM #Hold_sp_lock GROUP BY SPID, Type, DB_NAME(DBID), MODE Order by lock_count desc, DBName, SPID, MODE Show any blocked or blocking processes IF EXISTS ( SELECT Name FROM tempdb sysobjects Where name like '#Catch_SPID%' ) If So Drop it DROP TABLE #Catch_SPID GO Create Table #Catch_SPID ( bSPID int, BLK_Status char(10) ) GO Insert into #Catch_SPID Select Distinct SPID, 'BLOCKED' from master sysprocesses where blocked <> 0 UNION 5 – DBA as detective 138 Select Distinct blocked, 'BLOCKING' from master sysprocesses where blocked <> 0 DECLARE @tSPID int DECLARE @blkst char(10) SELECT TOP 1 @tSPID = bSPID, @blkst = BLK_Status from #Catch_SPID WHILE( @@ROWCOUNT > 0 ) BEGIN PRINT 'DBCC Results for SPID ' + Cast(@tSPID as varchar(5)) + '( ' + rtrim(@blkst) + ' )' PRINT ' ' PRINT '' DBCC INPUTBUFFER(@tSPID) SELECT TOP 1 @tSPID = bSPID, @blkst = BLK_Status from #Catch_SPID WHERE bSPID > @tSPID Order by bSPID END Listing 5.3: Automated discovery query. There is nothing overly complicated about this query. It is a base starting point from which you can quickly analyze locking and blocking issues in SQL Server. In the case of non-blocking locks, it will show you any query that is a potential issue with regard to other resources such as memory or I/O. Figure 5.10 shows the output of this query, captured while the "Bad Query" was executing. 5 – DBA as detective 139 Figure 5.10: Output of SPID count and Blocking query in Automated Discovery. Notice the high lock count of 99 for SPID 51, the culprit query. The next output section shows that, in this case, SPID 51 is indeed causing blocking, and the code that the SPID is executing follows, as we have seen previously from DBCC INPUTBUFFER. In addition, the Automated Discovery Query also lists all of the blocked SPIDs behind the main blocking SPID. Figure 5.11 shows the queries, in this case simple select statements against the Important_Data table, which are blocked by SPID 51. . It is a base starting point from which you can quickly analyze locking and blocking issues in SQL Server. In the case of non-blocking locks, it will show you any query that is a potential issue. used sp_who2 to seek out SPIDs that are causing blocking issues, DBCC INPUTBUFFER to elicit the SQL being executed by such a blocking SPID, and then sp_lock to discover some information about