Time ago, we wrote an article Lesson Learned #22: How to identify blocking issues? today,
I would like to enhance this topic by introducing a monitoring system that expands on that guide. This PowerShell script not only identifies blocking issues but also calculates the total, maximum, average, and minimum blocking times.
My idea is to run this PowerShell script, which executes T-SQL queries to identify blocking issues, showing the impact of the blocking and the blocking chains every 5 seconds. The script will save the details in a file for further review.
# Configure the connection string and folder for log file
$connectionString = "Server=tcp:servername.database.windows.net,1433;Database=dbname;User ID=username;Password=pwd!;Encrypt=true;Connection Timeout=30;"
$Folder = "c:\SQLDAta"
# Function to get and display blocking statistics
function Get-BlockingStatistics {
$query = "
select conn.session_id as blockerSession,
conn2.session_id as BlockedSession,
req.wait_time as Waiting_Time_ms,
cast((req.wait_time/1000.) as decimal(18,2)) as Waiting_Time_secs,
cast((req.wait_time/1000./60.) as decimal(18,2)) as Waiting_Time_mins,
t.text as BlockerQuery,
t2.text as BlockedQuery,
req.wait_type from sys.dm_exec_requests as req
inner join sys.dm_exec_connections as conn on req.blocking_session_id=conn.session_id
inner join sys.dm_exec_connections as conn2 on req.session_id=conn2.session_id
cross apply sys.dm_exec_sql_text(conn.most_recent_sql_handle) as t
cross apply sys.dm_exec_sql_text(conn2.most_recent_sql_handle) as t2
"
$connection = Connect-WithRetry -connectionString $connectionString -maxRetries 5 -initialDelaySeconds 2
if ($connection -ne $null)
{
$blockings = Execute-SqlQueryWithRetry -connection $connection -query $query -maxRetries 5 -initialDelaySeconds 2
$connection.Close()
}
if ($blockings.Count -gt 0) {
$totalBlockings = $blockings.Count
$maxWaitTime = $blockings | Measure-Object -Property WaitTimeSeconds -Maximum | Select-Object -ExpandProperty Maximum
$minWaitTime = $blockings | Measure-Object -Property WaitTimeSeconds -Minimum | Select-Object -ExpandProperty Minimum
$avgWaitTime = $blockings | Measure-Object -Property WaitTimeSeconds -Average | Select-Object -ExpandProperty Average
logMsg "Total blockings: $totalBlockings" (1)
logMsg "Maximum blocking time (seconds): $maxWaitTime" (2)
logMsg "Minimum blocking time (seconds): $minWaitTime" (2)
logMsg "Average blocking time (seconds): $avgWaitTime" (2)
logMsg "-- -- -- -- Blocking chain details: -- -- " (1)
foreach ($blocking in $blockings)
{
logMsg "Blocked Session ID: $($blocking.SessionId)"
logMsg "Wait Time (seconds): $($blocking.WaitTimeSeconds)"
logMsg "Blocker Session ID: $($blocking.BlockingSessionId)"
logMsg "Blocked SQL Text: $($blocking.SqlText)"
logMsg "Blocker SQL Text: $($blocking.BlockingSqlText)"
logMsg "---------------------------------------------"
}
} else {
logMsg "No blockings found at this time."
}
}
# Function to execute a SQL query with retry logic
function Execute-SqlQueryWithRetry {
param (
[System.Data.SqlClient.SqlConnection]$connection,
[string]$query,
[int]$maxRetries = 5,
[int]$initialDelaySeconds = 2
)
$attempt = 0
$success = $false
$blockings = @()
while (-not $success -and $attempt -lt $maxRetries) {
try {
$command = $connection.CreateCommand()
$command.CommandText = $query
$reader = $command.ExecuteReader()
while ($reader.Read()) {
$blockingE = New-Object PSObject -Property @{
SessionId = $reader["BlockedSession"]
WaitTimeSeconds = $reader["Waiting_Time_secs"]
BlockingSessionId = $reader["BlockerSession"]
SqlText = $reader["BlockedQuery"]
BlockingSqlText = $reader["BlockerQuery"]
}
$blockings+=$blockingE
}
$success = $true
} catch {
$attempt++
if ($attempt -lt $maxRetries) {
logMsg "Query execution attempt $attempt failed. Retrying in $initialDelaySeconds seconds..." 2
Start-Sleep -Seconds $initialDelaySeconds
$initialDelaySeconds *= 2 # Exponential backoff
} else {
logMsg "Query execution attempt $attempt failed. No more retries." 2
throw $_
}
}
}
return ,($blockings)
}
#--------------------------------
#Log the operations
#--------------------------------
function logMsg
{
Param
(
[Parameter(Mandatory=$true, Position=0)]
[string] $msg,
[Parameter(Mandatory=$false, Position=1)]
[int] $Color,
[Parameter(Mandatory=$false, Position=2)]
[boolean] $Show=$true,
[Parameter(Mandatory=$false, Position=3)]
[string] $sFileName,
[Parameter(Mandatory=$false, Position=4)]
[boolean] $bShowDate=$true,
[Parameter(Mandatory=$false, Position=5)]
[boolean] $bSaveOnLogFile=$true
)
try
{
if($bShowDate -eq $true)
{
$Fecha = Get-Date -format "yyyy-MM-dd HH:mm:ss"
$msg = $Fecha + " " + $msg
}
If( TestEmpty($SFileName) )
{
Write-Output $msg | Out-File -FilePath $LogFile -Append
}
else
{
Write-Output $msg | Out-File -FilePath $sFileName -Append
}
$Colores="White"
$BackGround =
If($Color -eq 1 )
{
$Colores ="Cyan"
}
If($Color -eq 3 )
{
$Colores ="Yellow"
}
if($Color -eq 2 -And $Show -eq $true)
{
Write-Host -ForegroundColor White -BackgroundColor Red $msg
}
else
{
if($Show -eq $true)
{
Write-Host -ForegroundColor $Colores $msg
}
}
}
catch
{
Write-Host $msg
}
}
#--------------------------------
#Validate Param
#--------------------------------
function TestEmpty($s)
{
if ([string]::IsNullOrWhitespace($s))
{
return $true;
}
else
{
return $false;
}
}
#--------------------------------------------------------------
#Create a folder
#--------------------------------------------------------------
Function CreateFolder
{
Param( [Parameter(Mandatory)]$Folder )
try
{
$FileExists = Test-Path $Folder
if($FileExists -eq $False)
{
$result = New-Item $Folder -type directory
if($result -eq $null)
{
logMsg("Imposible to create the folder " + $Folder) (2)
return $false
}
}
return $true
}
catch
{
return $false
}
}
function GiveMeFolderName([Parameter(Mandatory)]$FolderSalida)
{
try
{
$Pos = $FolderSalida.Substring($FolderSalida.Length-1,1)
If( $Pos -ne "\" )
{return $FolderSalida + "\"}
else
{return $FolderSalida}
}
catch
{
return $FolderSalida
}
}
#-------------------------------
#Create a folder
#-------------------------------
Function DeleteFile{
Param( [Parameter(Mandatory)]$FileName )
try
{
$FileExists = Test-Path $FileNAme
if($FileExists -eq $True)
{
Remove-Item -Path $FileName -Force
}
return $true
}
catch
{
return $false
}
}
# Function to connect to the database with retry logic
function Connect-WithRetry {
param (
[string]$connectionString,
[int]$maxRetries = 5,
[int]$initialDelaySeconds = 2
)
$attempt = 0
$connection = $null
while (-not $connection -and $attempt -lt $maxRetries) {
try {
$connection = New-Object System.Data.SqlClient.SqlConnection
$connection.ConnectionString = $connectionString
$connection.Open()
} catch {
$attempt++
if ($attempt -lt $maxRetries) {
logMsg "Connection attempt $attempt failed. Retrying in $initialDelaySeconds seconds..." 2
Start-Sleep -Seconds $initialDelaySeconds
$initialDelaySeconds *= 2 # Exponential backoff
} else {
logMsg "Connection attempt $attempt failed. No more retries." 2
throw $_
}
}
}
return $connection
}
clear
$result = CreateFolder($Folder) #Creating the folder that we are going to have the results, log and zip.
If( $result -eq $false)
{
write-host "Was not possible to create the folder"
exit;
}
$sFolderV = GiveMeFolderName($Folder) #Creating a correct folder adding at the end \.
$LogFile = $sFolderV + "Blockings.Log" #Logging the operations.
logMsg("Deleting Operation Log file") (1)
$result = DeleteFile($LogFile) #Delete Log file
logMsg("Deleted Operation Log file") (1)
# Loop to run the monitoring every 5 seconds
while ($true) {
Clear-Host
Get-BlockingStatistics
Start-Sleep -Seconds 5
}
Please note that this script is provided as-is and without any warranty. Use it at your own risk. Always test scripts in a development environment before deploying them to production.
Updated May 21, 2024
Version 2.0Jose_Manuel_Jurado
Microsoft
Joined November 29, 2018
Azure Database Support Blog
Follow this blog board to get notified when there's new activity