Unless special monitoring tools like Redgate’s SQL Monitor are available, when it comes to troubleshooting blocking sessions one can use the sp_who2 and sp_whoisactive stored procedures or the further available DMVs, which can prove useful upon case. In the case one just needs to find the root session that blocks other sessions, as it happens in poorly designed and/or heavily used databases, one can follow the chain of blockings provided by the sp_who2, though it requires time. Therefore, based on sp_who2 logic I put together a script that with the help of common tables expressions walks through the hierarchical chain to identify the root blocking session(s):
;WITH CTERequests AS ( -- taking a snapshot of requests SELECT ER.session_id , ER.request_id , ER.blocking_session_id , ER.database_id , ER.status , ER.sql_handle , ER.command , ER.start_time , ER.total_elapsed_time/(1000*1000) total_elapsed_time , ER.cpu_time/(1000*1000) cpu_time , ER.wait_type , ER.wait_time/(1000*1000) wait_time , ER.reads , ER.writes , ER.granted_query_memory/128.0 granted_query_memory_mb FROM sys.dm_exec_requests ER with (nolock) WHERE ER.session_id > 50 – user sessions ), CTE AS ( -- building the tree SELECT ER.session_id , ER.request_id , ER.blocking_session_id , ER.database_id , ER.status , ER.command , ER.sql_handle , Cast(NULL as varbinary(64)) blocking_sql_handle , 0 level , CAST(ER.session_id as nvarchar(max)) sessions_path , ER.start_time , ER.total_elapsed_time , ER.cpu_time , ER.wait_type , ER.wait_time , ER.reads , ER.writes , ER.granted_query_memory_mb FROM CTERequests ER -- WHERE ER.blocking_session_id <>0 -- only blocked sessions UNION ALL SELECT ER.session_id , ER.request_id , BR.blocking_session_id , ER.database_id , ER.status , ER.command , ER.sql_handle , BR.sql_handle blocking_sql_handle , ER.level + 1 level , ER.sessions_path + '/' + Cast(BR.session_id as nvarchar(10)) sessions_path , ER.start_time , ER.total_elapsed_time , ER.cpu_time , ER.wait_type , ER.wait_time , ER.reads , ER.writes , ER.granted_query_memory_mb FROM CTERequests br JOIN CTE ER ON BR.session_id = ER.blocking_session_id AND BR.session_id <> ER.session_id ) -- the final query SELECT CTE.session_id , CTE.blocking_session_id , CTE.request_id , DB_NAME(CTE.database_id) database_name , CTE.status , CTE.start_time , BES.last_request_end_time , CTE.level , CTE.sessions_path , CTE.command , CTE.sql_handle , est.text sql_text , CTE.blocking_sql_handle , best.text blocking_sql_text , CTE.total_elapsed_time , CTE.cpu_time , CTE.wait_type , CTE.wait_time , CTE.reads , CTE.writes , CTE.granted_query_memory_mb , TSU.request_internal_objects_alloc_page_count/128.0 allocated_mb , TSU.request_internal_objects_dealloc_page_count/128.0 deallocated_mb , ES.login_name , ES.[host_name] , BES.[host_name] blocking_host_name FROM CTE LEFT JOIN sys.dm_exec_sessions ES with (nolock) ON CTE.session_id = ES.session_id LEFT JOIN sys.dm_exec_sessions BES with (nolock) ON CTE.blocking_session_id = BES.session_id LEFT JOIN ( -- allocated space SELECT session_id , request_id , SUM(internal_objects_alloc_page_count) AS request_internal_objects_alloc_page_count , SUM(internal_objects_dealloc_page_count)AS request_internal_objects_dealloc_page_count FROM sys.dm_db_task_space_usage with (nolock) WHERE session_id > 50 GROUP BY session_id , request_id ) TSU ON TSU.request_id = CTE.request_id AND TSU.session_id = CTE.session_id OUTER APPLY sys.dm_exec_sql_text(CTE.sql_handle) as est OUTER APPLY sys.dm_exec_sql_text(CTE.blocking_sql_handle) as best WHERE CTE.level IN (SELECT MAX(B.level) -- longest path per session FROM CTE B WHERE CTE.session_id = B.session_id GROUP by B.session_id) ORDER BY CTE.session_id
Notes:
The script considers only the user sessions (session_id>50), however the constraint can be left out in order to get also the system sessions.
Over the years I discovered also 1-2 exceptions where the logic didn’t work as expected. Unfortunately, not having historical data made it difficult to find the cause.
Killing the root blocking session usually makes the other blockings disappear, however it’s not always the case.
There can multiple root blocking sessions as well.
For the meaning of the above attributes see the MSDN documentation: sys.dm_exec_requests, sys.dm_exec_sessions, sys.dm_db_task_space_usage, sys.dm_exec_sql_text.
Be careful when killing the sessions (see my other blog post)!
Happy coding!
No comments:
Post a Comment