What’s Happening in SQL Server?

One of the most important tools for your toolbox is some mechanism to see what’s happening in the engine. Who is running what in my system, and how is it behaving? I liken this to “Task Manager” or “Resource Monitor” in Windows. SQL Server has “Activity Monitor” as a graphical tool within SQL Server Management Studios, but I’ve found that to be a bit limiting.

There are many other fantastic tools for such diagnostics… Off the top of my head, I’d recommend looking at sp_Blitz from Brent Ozar and team or sp_WhoIsActive from Adam Machanic. I have not used them a lot myself, but I’ve heard nothing but great things about each tool.

I have developed the following script over the course of several years. I have iterated over it and updated it hundreds of times. I used it daily, and I’m never done changing it. So why write my own? A few reasons (none of them may be particularly compelling to you):

  1. It’s been a great learning experience to dive in and discover SQL’s DMVs
  2. It’s extremely customized for my needs (what can I say- I like what I like)
  3. It’s a no-trace solution… meaning that it’s just a SELECT statement, and no database objects are created, nor are any settings or configurations changed

I believe the only permission you’ll need on your SQL instance is VIEW SERVER STATE. It should also be compatible with at least SQL 2008 – SQL 2017. Furthermore, it’s written with dynamic SQL so it will return more information with newer versions of SQL, as Microsoft has augmented DMVs over time.

Keep in mind, I never claimed this code was pretty 😉

Without further ado, here it is:

/*
Written by: Ryan Snee (http://www.sqlsnee.com)

*/

DECLARE @ShowSleepingThreads BIT;
DECLARE @SkipQP BIT;
DECLARE @V  varchar(50);
DECLARE @V1 INT;
DECLARE @V2 INT;
DECLARE @V3 INT;



/*<Setable Parameters>*/
SET @ShowSleepingThreads=0;
SET @SkipQP=0;					--Sometimes this query will be blocked when querying the sys.dm_exec_query_plan DMF
/*</Setable Parameters>*/




select @V=cast(SERVERPROPERTY ('ProductVersion')  as varchar(50));

SELECT @V1=CAST(substring(@V,1,charindex('.',@V)-1) as int)
,@V2=CAST(substring(@V,charindex('.',@V)+1,(charindex('.',@V,charindex('.',@V)+1))-(charindex('.',@V)+1)) as int)
,@V3=CAST(substring(@V,	charindex('.',@V,CHARINDEX('.',@V)+1)+1,(charindex('.',@V,charindex('.',@V,CHARINDEX('.',@V)+1)+1))-(charindex('.',@V,CHARINDEX('.',@V)+1)+1)) as int);


declare @sql nvarchar(max);
SET @sql= CAST('' AS nvarchar(max))
+N'SELECT
	cast(s.session_id  as varchar(10))
		+ case when (r.blocking_session_id=0 or r.blocking_session_id is null) and LB.LeadBlocker=1 then ''*'' else '''' end
	AS [Session]
	,'
	+CASE
	WHEN @V1 >= 11 THEN N'concat(case when dbt.database_transaction_status2=258 then ''ROLLBACK'' ELSE s.status END,'':''+r.status)'
	ELSE N'case when dbt.database_transaction_status2=258 then ''ROLLBACK'' ELSE (s.status) END --This column is undocumented by MS, and this is not guranteed to be accurate!!!' 
	END+N' as [Status]
	
	
	,CASE
	
		WHEN ((datediff(day,s.last_request_start_time,getdate())>28) AND (r.session_id IS NOT NULL))
			THEN cast(datediff(day,s.last_request_start_time,getdate()) as varchar(50))+''d+''
		WHEN [r].[total_elapsed_time] > 86400000 --Days, hours, and minutes
			THEN cast([r].[total_elapsed_time]/86400000 as varchar(50)) +''d ''+cast(([r].[total_elapsed_time]/3600000)%24 as varchar(50)) +''h ''+cast(([r].[total_elapsed_time]/60000)%60 as varchar(50)) +''m''
		WHEN [r].[total_elapsed_time] > 3600000 --Hours, minutes and seconds
			THEN cast([r].[total_elapsed_time]/3600000 as varchar(50)) +''h ''+cast(([r].[total_elapsed_time]/60000)%60 as varchar(50)) +''m ''+cast(([r].[total_elapsed_time]/1000)%60 as varchar(50))+''s''
		WHEN [r].[total_elapsed_time] > 60000 --Minutes, seconds, and milliseconds
			THEN cast([r].[total_elapsed_time]/60000 as varchar(50)) +''m ''+cast(([r].[total_elapsed_time]/1000)%60 as varchar(50))+''s ''+cast(([r].[total_elapsed_time])%1000 as varchar(50))+''ms''
		WHEN [r].[total_elapsed_time] > 1000 --Seconds and milliseconds
			THEN cast([r].[total_elapsed_time]/1000 as varchar(50))+''s ''+cast(([r].[total_elapsed_time])%1000 as varchar(50))+''ms''
		ELSE cast([r].[total_elapsed_time] as varchar(50))+''ms'' --Milliseconds

	END AS [ElapsedTime]
	
	
	
	,coalesce(nullif(s.host_name+'' '',''''),''(''+c.client_net_address+'')'') as Client
	
	,

	case
		when s.login_name <> s.original_login_name
			THEN s.login_name +'' '' +quotename(s.original_login_name ,''('')
		ELSE s.login_name
	END AS [login_name]
	
	,coalesce(s.program_name,'''')+ coalesce('' (PID:''+cast(s.host_process_id as varchar(20))+'')'','''') as [ClientProgram]
	
	,'+CASE
	WHEN @V1 >= 11 THEN N'DB_NAME(COALESCE(r.database_id, s.database_id))'
	ELSE N'DB_NAME(r.database_id)'
	END+N' AS [Database Name]
	
	
	,nullif(r.percent_complete,0) AS [%Complete]
	

	

	,nullif(
		case
			WHEN(
				select count(distinct scheduler_id)
				FROM sys.dm_os_tasks ost
				where r.request_id=ost.request_id and s.session_id=ost.session_id
				--and ost.task_state = ''RUNNING''
				and scheduler_id < 1000000
			) > 1
			THEN(
				select count(distinct scheduler_id)
				FROM sys.dm_os_tasks ost
				where r.request_id=ost.request_id and s.session_id=ost.session_id
				--and ost.task_state = ''RUNNING''
				and scheduler_id < 1000000
			)-1
			ELSE(
				select count(distinct scheduler_id)
				FROM sys.dm_os_tasks ost
				where r.request_id=ost.request_id and s.session_id=ost.session_id
				--and ost.task_state = ''RUNNING''
				and scheduler_id < 1000000
			)
		END
	,0) as [Tasks]
	,'+CASE
	WHEN @V1 >= 11 THEN N'format(coalesce(r.cpu_time,s.cpu_time),''#,###'')'
	ELSE N'coalesce(r.cpu_time,s.cpu_time)'
	END+N' AS [CPU(ms)]
	
	
	,CASE
		WHEN eqmg.granted_memory_kb >= 1048576
			THEN cast(cast(eqmg.granted_memory_kb/1048576.0 as decimal(18,2)) as varchar(30))+ '' GB''
		WHEN eqmg.granted_memory_kb >= 1024
			THEN cast(cast(eqmg.granted_memory_kb/1024.0 as decimal(18,2)) as varchar(30))+ '' MB''
		ELSE
			cast(eqmg.granted_memory_kb as varchar(30))+ '' KB''
	 END AS [granted_mem]
	,CASE
		WHEN ([dtdt].[TLogBytesUsed]) > 1073741824000 THEN CAST(CAST((([dtdt].[TLogBytesUsed]) / 1024.0/1024.0/1024.0/1024.0) AS DECIMAL(18,2)) AS VARCHAR(20)) + '' TB''
		WHEN ([dtdt].[TLogBytesUsed]) > 1048576000 THEN CAST(CAST((([dtdt].[TLogBytesUsed]) / 1024.0/1024.0/1024.0) AS DECIMAL(18,2)) AS VARCHAR(20)) + '' GB''
		WHEN ([dtdt].[TLogBytesUsed]) > 1024000 THEN CAST(CAST((([dtdt].[TLogBytesUsed]) / 1024.0/1024.0) AS DECIMAL(18,2)) AS VARCHAR(20)) + '' MB''
		WHEN ([dtdt].[TLogBytesUsed]) > 1000 THEN CAST(CAST((([dtdt].[TLogBytesUsed]) / 1024.0) AS DECIMAL(18,2)) AS VARCHAR(20)) + '' KB''
		ELSE CAST(CAST((([dtdt].[TLogBytesUsed]) ) AS DECIMAL(18,2)) AS VARCHAR(20)) + '' B''
	END as [TLogBytesUsed]

,'+case WHEN @V1 >= 11 THEN N'format(try_cast(eqmg.query_cost as decimal(38,0)),''#,###'')' else 'cast(eqmg.query_cost as decimal(38,0))' END +' AS [estQueryCost]	
	

	


	,cast((cast(coalesce(r.logical_reads,s.logical_reads) as bigint)*8)/1024.0/1024. as decimal(18,2)) as [LogicalReads(GB)]
	,CASE
	WHEN (r.logical_reads IS NOT NULL AND r.reads IS NOT NULL AND r.logical_reads <> 0)
		THEN CAST(
			(1-(CAST(r.reads as decimal)/cast(r.logical_reads as decimal)))*100
		
		as decimal(18,2))
	WHEN s.logical_reads <> 0
		THEN CAST((1-(CAST(s.reads as decimal)/cast(s.logical_reads as decimal)))*100 as decimal(18,2))
	ELSE
		NULL
	END AS [BPHitRate]
	
	

	,case when r.wait_type = ''CXPACKET'' THEN coalesce(ParallelWait.wait_duration_ms, r.wait_time)  ELSE r.wait_time END AS [wait_time(ms)]
	,case when r.wait_type = ''CXPACKET'' THEN coalesce(ParallelWait.wait_type, r.wait_type)+''*''  ELSE r.wait_type END AS [wait_type]
	,case when r.wait_type = ''CXPACKET'' THEN coalesce(ParallelWait.wait_resource, r.wait_resource)  ELSE r.wait_resource END AS [wait_resource]'+
	case when @V1 >= 13
	THEN N'
	,SesWaits.wait_type + '' (''+CAST(SesWaits.wait_time_ms as varchar(10))+'')'' as [BiggestSessionWait (ms)]'
	ELSE N''
	END +N'




	,nullif(r.blocking_session_id, 0) AS [Blocker]
	,r.last_wait_type
	
	
	
	,r.command



	,CASE
		WHEN s.transaction_isolation_level=1 THEN ''*Read Uncommitted*''
		WHEN s.transaction_isolation_level=2 THEN ''Read Committed''
		WHEN s.transaction_isolation_level=3 THEN ''*Repeatable Read*''
		WHEN s.transaction_isolation_level=4 THEN ''*Serializable*''
		WHEN s.transaction_isolation_level=5 THEN ''*Snapshot*''
		ELSE ''Unspecified''
	END AS [Transaction Isolation]
	
	,c.auth_scheme
	,c.net_transport
	,c.encrypt_option
	
	,CASE
		WHEN at.transaction_state=0 THEN ''The transaction has not been completely initialized yet.''
		WHEN at.transaction_state=1 THEN ''The transaction has been initialized but has not started.''
		WHEN at.transaction_state=2 THEN ''The transaction is active.''
		WHEN at.transaction_state=3 THEN ''The transaction has ended. This is used for read-only transactions.''
		WHEN at.transaction_state=4 THEN ''The commit process has been initiated on the distributed transaction. This is for DTs only. The DT is still active but further processing cannot take place.''
		WHEN at.transaction_state=5 THEN ''The transaction is in a prepared state and waiting resolution.''
		WHEN at.transaction_state=6 THEN ''The transaction has been committed.''
		WHEN at.transaction_state=7 THEN ''The transaction is being rolled back.''
		WHEN at.transaction_state=8 THEN ''The transaction has been rolled back.''
		ELSE ''Unspecified''
	END AS [Transaction_State_desc]

	
	
	,at.transaction_begin_time
	,coalesce(st.open_transaction_count ,0) as OpenTranCount
	,case WHEN st.is_local =1
	THEN cast(at.transaction_id as varchar(50))
	ELSE cast(at.transaction_id as varchar(50))+''*''
	END AS transaction_id
	

	
	,N''(''+cast('+
	case when @V1 > 11
	THEN N'FORMAT('
	ELSE N''
	END +
	N'r.statement_start_offset'+
CASE WHEN @V1 > 11
THEN N', ''#,###'')'
ELSE N''
END +

	N' as nvarchar(max))+N'') ''+txt.text AS [BatchExecuting]
	
	,'+CASE WHEN
	
	(
		(@V1 >= 13)
		OR (@V1 = 12
			AND (
				(@V2 > 0)
				OR (@V2=0 AND  @V3 >= 5000)
			)
		)
	) --13 or > 12.0.5000
	THEN N'buf.event_info'
	ELSE N'''DBCC INPUTBUFFER(''+cast(s.session_id as varchar(10))+'');'''
	END +N'
	 AS [InputBuffer]
	
	'+CASE WHEN ((@V1 >= 13) OR (@V1 = 12 AND ((@V2 > 0) OR (@V2=0 AND  @V3 >= 5000)))) --13 or > 12.0.5000
	THEN N',buf.parameters AS BufParameters'
	ELSE N''
	END +N'
	 

	--Greg Larsen
	,SUBSTRING(txt.text,
		CASE WHEN r.statement_start_offset = 0
			OR r.statement_start_offset IS NULL
				THEN 1
				ELSE r.statement_start_offset/2 + 1 END,
		CASE WHEN r.statement_end_offset = 0
				OR r.statement_end_offset = -1
				OR r.statement_end_offset IS NULL
					THEN LEN(txt.text)
					ELSE r.statement_end_offset/2 END -
		 	CASE WHEN r.statement_start_offset = 0
				OR r.statement_start_offset IS NULL
					THEN 1
					ELSE r.statement_start_offset/2 END + 2
	) AS [Statement]
	
	,qs.creation_time as [PlanCompiled]
	,CASE
		WHEN r.plan_handle =0x0
			THEN ''''
		ELSE ''DBCC FREEPROCCACHE (''+convert(varchar(200),r.plan_handle,1)+'');''
	END as [RemovePlanFromCacheCmd]
	
	'+case when @SkipQP=0 THEN ' ,qp.query_plan' ELSE '' END +'
	
	,[txt2].text AS [MostRecentQuery]
	,s.login_time
	,CASE c.endpoint_id
		WHEN 1
		THEN 1
		ELSE 0
	END AS [isDAC]	
	

	
	

	,quotename(db_name(txt.dbid))+''.''+quotename(object_schema_name(txt.objectid,cast(txt.dbid as int)))+''.''+quotename(object_name(txt.objectid,cast(txt.dbid as int))) AS [ObjectName]
	
	
	,s.row_count as [RowsReturnedLastRequest]
	,r.row_count as [RequestRowCount]
	,m.text as [LastErrorEncountered]
	
	,wlg.name AS [WorkloadGroup]
	,rp.name AS [ResourcePool]
	
	
	,s.client_interface_name
	,net_packet_size
	,CASE
		WHEN (cast(c.num_reads as bigint)*net_packet_size) >= 1073741824
			THEN cast(cast((cast(c.num_reads as bigint)*net_packet_size)/1073741824.0 as decimal(18,2)) as varchar(30))+ '' GB''
		WHEN (cast(c.num_reads as bigint)*net_packet_size) >= 1048576
			THEN cast(cast((cast(c.num_reads as bigint)*net_packet_size)/1048576.0 as decimal(18,2)) as varchar(30))+ '' MB''
		WHEN (cast(c.num_reads as bigint)*net_packet_size) >= 1024
			THEN cast(cast((cast(c.num_reads as bigint)*net_packet_size)/1024.0 as decimal(18,2)) as varchar(30))+ '' KB''
		ELSE
			cast((cast(c.num_reads as bigint)*net_packet_size) as varchar(30))+ '' B''
	 END AS [NetworkBytesIngress]
	
	,CASE
		WHEN (cast(c.num_writes as bigint)*net_packet_size) >= 1073741824
			THEN cast(cast((cast(c.num_writes as bigint)*net_packet_size)/1073741824.0 as decimal(18,2)) as varchar(30))+ '' GB''
		WHEN (cast(c.num_writes as bigint)*net_packet_size) >= 1048576
			THEN cast(cast((cast(c.num_writes as bigint)*net_packet_size)/1048576.0 as decimal(18,2)) as varchar(30))+ '' MB''
		WHEN (cast(c.num_writes as bigint)*net_packet_size) >= 1024
			THEN cast(cast((cast(c.num_writes as bigint)*net_packet_size)/1024.0 as decimal(18,2)) as varchar(30))+ '' KB''
		ELSE
			cast((cast(c.num_writes as bigint)*net_packet_size) as varchar(30))+ '' B''
	 END AS [NetworkBytesEgress]
	,s.last_request_start_time
	,getdate() as [TimeRun]
	
from sys.dm_exec_sessions [s]
	LEFT JOIN sys.dm_exec_requests [r]
		ON s.session_id=r.session_id

	OUTER APPLY (
		SELECT top 1 *
		FROM sys.dm_exec_connections con
		where s.session_id=con.session_id
		--Grab a connection even if we don''t have a corresponding request
			and (r.connection_id=con.connection_id OR r.connection_id is null)
		--In that case, pick any connection related to this session that has a SQL handle... no good way to determine most recent
		ORDER BY CASE WHEN con.most_recent_sql_handle = 0x0 THEN 0 ELSE 1 END DESC
		
	) AS c
		
	OUTER APPLY sys.dm_exec_sql_text(c.[most_recent_sql_handle]) AS [txt2]
	
	left JOIN sys.dm_exec_query_stats AS [qs]
		ON qs.plan_handle=r.plan_handle
			AND r.statement_start_offset=qs.statement_start_offset
			AND r.statement_end_offset=qs.statement_end_offset
	OUTER APPLY sys.dm_exec_sql_text(r.sql_handle) AS [txt]

	'+case when @SkipQP=0 THEN '
	--Including exec_query_plan can cause this query to be blocked
	OUTER APPLY sys.dm_exec_query_plan(r.plan_handle) AS [qp]' ELSE '' END+ '

	LEFT JOIN sys.dm_tran_session_transactions [st]
		ON s.session_id=st.session_id
	LEFT JOIN sys.dm_tran_active_transactions [at]
		ON st.transaction_id=at.transaction_id
	
	
	
	outer apply(SELECT TOP 1 [database_transaction_status2] FROM sys.dm_tran_database_transactions dbt WHERE st.transaction_id=dbt.transaction_id and database_transaction_status2=258) dbt
	
	LEFT JOIN sys.dm_resource_governor_workload_groups wlg
		on s.group_id=wlg.group_id
	LEFT JOIN sys.dm_resource_governor_resource_pools rp
		on wlg.pool_id=rp.pool_id

	LEFT JOIN sys.messages m
		on s.prev_error=m.message_id
			AND language_id=1033 --English
	
	OUTER APPLY (SELECT TOP 1 wait_type ,wait_duration_ms,wait_resource from sys.dm_os_waiting_tasks a where a.session_id=s.session_id ORDER BY CASE WHEN wait_type=''CXPACKET'' THEN 2 when wait_type=''SLEEP_TASK'' THEN 1 ELSE 0 END, wait_duration_ms desc) ParallelWait
	LEFT JOIN sys.dm_exec_query_memory_grants eqmg
		ON s.session_id=eqmg.session_id
		AND r.request_id=eqmg.request_id
	OUTER APPLY (SELECT sum(database_transaction_log_bytes_used) as TLogBytesUsed FROM sys.dm_tran_database_transactions tr WHERE tr.transaction_id= at.transaction_id) AS dtdt
	'
	+CASE WHEN ((@V1 >= 13) OR (@V1 = 12 AND ((@V2 > 0) OR (@V2=0 AND  @V3 >= 5000)))) --13 or > 12.0.5000
	THEN N'
	OUTER APPLY sys.dm_exec_input_buffer(s.session_id, r.request_id) buf'
	ELSE N''
	END+

	CASE WHEN @V1>=13
	THEN N'
	--Biggest wait type (by time) besides CXPACKET
	OUTER APPLY (SELECT top 1 * FROM sys.dm_exec_session_wait_stats desws WHERE desws.session_id=s.session_id AND wait_time_ms > 0 ORDER BY case when wait_type=''CXPACKET'' THEN 1 ELSE 0 END, wait_time_ms desc) SesWaits'
	ELSE N''
	END+
	

	N'
	OUTER APPLY(select top 1 1 AS LeadBlocker from sys.dm_exec_requests r2 where s.session_id=r2.blocking_session_id) LB
WHERE 1=1
	AND (s.is_user_process = 1 OR command IN (''DB STARTUP'', ''ROLLBACK TRANSACTION'',''ALTER DATABASE ENCRYPTION KEY''))
	AND s.session_id <> @@SPID
	AND coalesce(s.program_name, '''') NOT LIKE ''%IntelliSense''
	--AND transaction_begin_time is not null
	AND ((r.wait_type IS NULL) OR (r.wait_Type <> ''SP_SERVER_DIAGNOSTICS_SLEEP''))'+
		CASE WHEN @ShowSleepingThreads=0 THEN N'
	AND ((s.status <> ''sleeping'') OR(transaction_state IS NOT NULL) OR  command IN (''DB STARTUP'', ''ROLLBACK TRANSACTION'',''ALTER DATABASE ENCRYPTION KEY''))'
	ELSE N'' END+N'
	--AND s.login_name LIKE ''%%''
	--AND '+CASE	WHEN @V1 >= 11 THEN N'DB_NAME(COALESCE(r.database_id, s.database_id))'	ELSE N'DB_NAME(r.database_id)'END+' LIKE ''%%''
ORDER BY
	--transaction_begin_time asc,
	dbt.database_transaction_status2 asc,
	s.status asc,
	CASE when r.status=''running'' THEN 1
	WHEN r.status=''runnable'' THEN 2
	WHEN r.status=''suspended'' THEN 3
	ELSE 4 END,
	
	
	CASE WHEN s.status=''sleeping''
		THEN s.session_id
	ELSE -1
	END DESC,
	s.last_request_start_time desc
	'

	

exec sp_executesql @sql
	

As a fun aside, it is possible to create this as a stored procedure, possibly with with filters (like limiting it to seeing sessions from a specific login), and allow somebody rights to execute just your pre-defined version of this query. This can be done without granting them the broad VIEW SERVER STATE, and can be accomplished through code-signing. While this is out-of-scope for this blog post, I may do one on that topic in the future.

The full script can be found here: SessionsAndRequests.txt.

Please let me know if you found this script useful or if you have any suggestions to improve it.

LLAP,
Ryan


As always, please note the waive of liability published on the About page.

The text of this entire blog site (including all scripts) is under the copyright of Ryan Snee. You are free to reproduce it, as outlined by the Creative Commons Attribution-ShareAlike 3.0 Unported License.