sql >> Databasteknik >  >> RDS >> Database

WhoIsActive Runner

Nuförtiden, inom SQL Server DBA-gemenskapen, är det extremt troligt att vi använder, eller åtminstone har hört talas om, den berömda lagrade proceduren sp_WhoIsActive utvecklad av Adam Machanic.

Under min tid som DBA använde jag SP för att omedelbart kontrollera vad som händer i en viss SQL Server-instans när den får alla "fingrar" att en viss applikation kör långsamt.

Det finns dock tillfällen där sådana problem blir återkommande och kräver ett sätt att fånga vad som händer för att hitta en potentiell gärningsman. Det finns också scenarier där du har flera instanser som fungerar som backend för 3-partsapplikationer. Den lagrade proceduren kan fungera bra för att hitta våra skyldiga.

I den här artikeln kommer jag att presentera ett PowerShell-verktyg som kan hjälpa vilken SQL Server DBA som helst att samla in frågor som identifierats av sp_WhoIsActive inuti en viss SQL Server-instans. Den SP skulle matcha dem med en viss söksträng och lagra dem i en utdatafil för efteranalys.

Inledande överväganden

Här är några antaganden innan du dyker in i detaljerna i skriptet:

  • Skriptet får namnet på instansen som en parameter. Om ingen godkänns, localhost kommer att antas av skriptet.
  • Skriptet kommer att be dig om en viss söksträng för att jämföra den med texterna i frågor som körs i SQL Server-instansen. Om det finns en matchning med någon av dem kommer den att lagras i en .txt-fil som du kan analysera senare.
  • Utdatafilen med all information relaterad till din instans genereras för den exakta sökvägen där PowerShell finns och utlöses. Se till att du har write behörigheter där.
  • Om du kör PowerShell-skriptet flera gånger för samma instans kommer alla tidigare befintliga utdatafiler att skrivas över. Endast den senaste kommer att behållas. Därför, om du behöver behålla en mycket specifik fil, spara den manuellt någon annanstans.
  • Paketet innehåller en .sql fil med koden för att distribuera WhoIsActive Stored Procedure till huvuddatabasen för den instans du anger. Skriptet kontrollerar om den lagrade proceduren redan finns i instansen och skapar den om den inte gör det.
    • Du kan välja att distribuera den till en annan databas. Se bara till de nödvändiga ändringarna i skriptet.
    • Ladda ned denna .sql fil från säker värd.
  • Skriptet försöker hämta informationen från SQL Server-instansen var tionde sekund som standard. Men om du vill använda ett annat värde, justera det därefter.
  • Se till att användaren som ansökt om att ansluta till SQL Server-instansen har behörighet att skapa och köra de lagrade procedurerna. Annars kommer den inte att uppnå sitt syfte.

Använda PowerShell-skriptet

Det här är vad du kan förvänta dig av skriptet:

Gå till platsen där du har lagt PowerShell-skriptfilen och kör den så här:

PS C:\temp> .\WhoIsActive-Runner.ps1 SERVER\INSTANCE

Jag använder C:\temp som exempel

Det enda skriptet kommer att fråga dig om är vilken typ av inloggning du vill använda för att ansluta till instansen.

Obs:Om du använder PowerShell ISE kommer uppmaningarna att se ut som skärmdumpar. Om du kör det direkt från PowerShell-konsolen kommer alternativen att uppmanas som text i samma fönster .

Betrodd – anslutningen till SQL Server-instansen kommer att göras med samma användare som för exekveringen av PowerShell-skriptet. Du behöver inte ange några referenser, det kommer att anta dem baserat på sammanhanget.

Windows-inloggning – du måste ange en Windows-inloggning för korrekt autentisering.

SQL-inloggning – du måste ange en SQL-inloggning för korrekt autentisering.

Oavsett vilket alternativ du väljer, se till att det har tillräckligt med behörigheter i instansen för att utföra kontroller .

Om du väljer den typ av inloggning som kräver att du anger autentiseringsuppgifter, kommer skriptet att meddela dig om ett misslyckande inträffar:

Med korrekt information specificerad kommer skriptet att kontrollera om SP finns i huvuddatabasen och fortsätter att skapa den om den inte gör det.

Se till att .sql-filen med T-SQL-koden för att skapa SP finns på samma sökväg där skriptet finns. .sql filnamnet måste vara sp_WhoIsActive.sql .

Om du vill använda ett annat .sql-filnamn och en annan måldatabas, se till de nödvändiga ändringarna i PowerShell-skriptet:

Nästa steg blir Söksträngsprompten . Du måste ange den för att samla in matchningar som returneras av varje exekveringsiteration av den lagrade proceduren i SQL Server-instansen.

Efter det måste du välja hur mycket tid du vill tillåta för skriptkörning.

I demonstrationssyfte kommer jag att välja alternativ #1 (5 min). Jag kommer att lämna en dummy-fråga igång i min instans. Frågan är WAITFOR DELAY ’00:10′ . Jag kommer att ange söksträngen WAITFOR så att du kan få en känsla av vad skriptet kommer att göra för dig.

När skriptet har slutfört sin körning kommer du att se en .txt fil som innehåller namnet på din instans och WhoIsActive som suffix.

Här är ett exempel på vad skriptet fångade och sparade i den .txt fil:

Fullständig kod för PowerShell-skriptet

Om du vill prova det här skriptet, använd koden nedan:

param(
    $instance = "localhost"
)

if (!(Get-Module -ListAvailable -Name "SQLPS")) {
    Write-Host -BackgroundColor Red -ForegroundColor White "Module Invoke-Sqlcmd is not loaded"
    exit
}

#Function to execute queries (depending on if the user will be using specific credentials or not)
function Execute-Query([string]$query,[string]$database,[string]$instance,[int]$trusted,[string]$username,[string]$password){
    if($trusted -eq 1){
        try{ 
            Invoke-Sqlcmd -Query $query -Database $database -ServerInstance $instance -ErrorAction Stop -ConnectionTimeout 5 -QueryTimeout 0      
        }
        catch{
            Write-Host -BackgroundColor Red -ForegroundColor White $_
            exit
        }
    }
    else{
        try{
            Invoke-Sqlcmd -Query $query -Database $database -ServerInstance $instance -Username $username -Password $password -ErrorAction Stop -ConnectionTimeout 5 -QueryTimeout 0
        }
         catch{
            Write-Host -BackgroundColor Red -ForegroundColor White $_
            exit
        }
    }
}

function Get-Property([string]$property,[string]$instance){
    Write-Host -NoNewline "$($property) " 
    Write-Host @greenCheck
    Write-Host ""
    switch($loginChoice){
        0       {$output = Execute-Query "SELECT SERVERPROPERTY('$($property)')" "master" $instance 1 "" ""}
        default {$output = Execute-Query "SELECT SERVERPROPERTY('$($property)')" "master" $instance 0 $login $password}   
    }
    switch($property){ 
        "EngineEdition"{
            switch($output[0]){
                1 {"$($property): Personal or Desktop Engine" | Out-File -FilePath $filePath -Append}
                2 {"$($property): Standard" | Out-File -FilePath $filePath -Append}
                3 {"$($property): Enterprise" | Out-File -FilePath $filePath -Append}
                4 {"$($property): Express" | Out-File -FilePath $filePath -Append}
                5 {"$($property): SQL Database" | Out-File -FilePath $filePath -Append}
                6 {"$($property): Microsoft Azure Synapse Analytics" | Out-File -FilePath $filePath -Append}
                8 {"$($property): Azure SQL Managed Instance" | Out-File -FilePath $filePath -Append}
                9 {"$($property): Azure SQL Edge" | Out-File -FilePath $filePath -Append}
                11{"$($property): Azure Synapse serverless SQL pool" | Out-File -FilePath $filePath -Append}            
            }
        }
        "HadrManagerStatus"{
            switch($output[0]){
                0       {"$($property): Not started, pending communication." | Out-File -FilePath $filePath -Append}
                1       {"$($property): Started and running." | Out-File -FilePath $filePath -Append}
                2       {"$($property): Not started and failed." | Out-File -FilePath $filePath -Append}
                default {"$($property): Input is not valid, an error, or not applicable." | Out-File -FilePath $filePath -Append}            
            }
        }
        "IsIntegratedSecurityOnly"{
            switch($output[0]){
                1{"$($property): Integrated security (Windows Authentication)" | Out-File -FilePath $filePath -Append}
                0{"$($property): Not integrated security. (Both Windows Authentication and SQL Server Authentication.)" | Out-File -FilePath $filePath -Append}                
            }
        }
        default{                        
            if($output[0] -isnot [DBNull]){
                "$($property): $($output[0])" | Out-File -FilePath $filePath -Append
            }else{
                "$($property): N/A" | Out-File -FilePath $filePath -Append
            }
        }
    }
    
    return
}

$filePath = ".\$($instance.replace('\','_'))_WhoIsActive.txt"
Remove-Item $filePath -ErrorAction Ignore

$loginChoices = [System.Management.Automation.Host.ChoiceDescription[]] @("&Trusted", "&Windows Login", "&SQL Login")
$loginChoice = $host.UI.PromptForChoice('', 'Choose login type for instance', $loginChoices, 0)
switch($loginChoice)
{
    1 { 
        $login          = Read-Host -Prompt "Enter Windows Login"
        $securePassword = Read-Host -Prompt "Enter Password" -AsSecureString
        $password       = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($securePassword))
      }
    2 { 
        $login          = Read-Host -Prompt "Enter SQL Login"
        $securePassword = Read-Host -Prompt "Enter Password" -AsSecureString
        $password       = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($securePassword))
      }
}

#Attempt to connect to the SQL Server instance using the information provided by the user
try{
    switch($loginChoice){
        0{
            $spExists = Execute-Query "SELECT COUNT(*) FROM sys.objects WHERE type = 'P' AND name = 'sp_WhoIsActive'" "master" $instance 1 "" ""
            if($spExists[0] -eq 0){
                Write-Host "The Stored Procedure doesn't exist in the master database."
                Write-Host "Attempting its creation..."
                try{
                    Invoke-Sqlcmd -ServerInstance $instance -Database "master" -InputFile .\sp_WhoIsActive.sql
                    Write-Host -BackgroundColor Green -ForegroundColor White "Success!"
                }
                catch{
                    Write-Host -BackgroundColor Red -ForegroundColor White $_
                    exit
                }
            }
        }
        default{
            $spExists = Execute-Query "SELECT COUNT(*) FROM sys.objects WHERE type = 'P' AND name = 'sp_WhoIsActive'" "master" $instance 0 $login $password
            if($spExists[0] -eq 0){
                Write-Host "The Stored Procedure doesn't exist in the master database."
                Write-Host "Attempting its creation..."
                try{
                    Invoke-Sqlcmd -ServerInstance $instance -Database "master" -Username $login -Password $password -InputFile .\sp_WhoIsActive.sql
                    Write-Host -BackgroundColor Green -ForegroundColor White "Success!"
                }
                catch{
                    Write-Host -BackgroundColor Red -ForegroundColor White $_
                    exit
                }
            }
        }   
    }     
}
catch{
    Write-Host -BackgroundColor Red -ForegroundColor White $_
    exit
}

#If the connection succeeds, then proceed with the retrieval of the configuration for the instance
Write-Host " _______  _______                           _______ _________ _______  _______  _______ __________________          _______ "
Write-Host "(  ____ \(  ____ )       |\     /||\     /|(  ___  )\__   __/(  ____ \(  ___  )(  ____ \\__   __/\__   __/|\     /|(  ____ \"
Write-Host "| (    \/| (    )|       | )   ( || )   ( || (   ) |   ) (   | (    \/| (   ) || (    \/   ) (      ) (   | )   ( || (    \/"
Write-Host "| (_____ | (____)| _____ | | _ | || (___) || |   | |   | |   | (_____ | (___) || |         | |      | |   | |   | || (__    "
Write-Host "(_____  )|  _____)(_____)| |( )| ||  ___  || |   | |   | |   (_____  )|  ___  || |         | |      | |   ( (   ) )|  __)   "
Write-Host "      ) || (             | || || || (   ) || |   | |   | |         ) || (   ) || |         | |      | |    \ \_/ / | (      "
Write-Host "/\____) || )             | () () || )   ( || (___) |___) (___/\____) || )   ( || (____/\   | |   ___) (___  \   /  | (____/\"
Write-Host "\_______)|/              (_______)|/     \|(_______)\_______/\_______)|/     \|(_______/   )_(   \_______/   \_/   (_______/"                                                                                                                            
Write-Host ""
$searchString = Read-Host "Enter string to lookup"  
$timerChoices = [System.Management.Automation.Host.ChoiceDescription[]] @("&1)5m", "&2)10m", "&3)15m","&4)30m","&5)Indefinitely")
$timerChoice  = $host.UI.PromptForChoice('', 'How long should the script run?', $timerChoices, 0)

Write-Host -NoNewline "Script will run "
switch($timerChoice){
    0{
        Write-Host "for 5 minutes."
        $limit = 5
    }
    1{
        Write-Host "for 10 minutes."
        $limit = 10
    }
    2{
        Write-Host "for 15 minutes."
        $limit = 15
    }
    3{
        Write-Host "for 30 minutes."
        $limit = 30
    }
    4{
        Write-Host "indefinitely (press ctrl-c to exit)."
        $limit = 2000000
    }
}
Write-Host "Start TimeStamp: $(Get-Date)"

$StopWatch = [system.diagnostics.stopwatch]::StartNew()

while($StopWatch.Elapsed.TotalMinutes -lt $limit){
    $results = Execute-Query "EXEC sp_WhoIsActive" "master" $instance 1 "" ""
    Get-Date | Out-File -FilePath $filePath -Append
    "####################################################################" | Out-File -FilePath $filePath -Append
    foreach($result in $results){
        if($result.sql_text -match $searchString){
            $result | Out-File -FilePath $filePath -Append
        }
        "####################################################################" | Out-File -FilePath $filePath -Append
    }
    Start-Sleep -s 10
}
Get-Date | Out-File -FilePath $filePath -Append
"####################################################################" | Out-File -FilePath $filePath -Append
Write-Host "End TimeStamp  : $(Get-Date)"

Slutsats

Låt oss komma ihåg att WhoIsActive inte kommer att fånga frågor som exekveras mycket snabbt av DB Engine. Men syftet med detta verktyg är att upptäcka de problematiska frågor som är långsamma och som kan dra nytta av en optimeringsrunda (eller omgångar).

Du kanske hävdar att en Profiler-spårning eller en utökad händelsesession skulle kunna åstadkomma samma sak. Men jag tycker att det är väldigt bekvämt att du helt enkelt kan starta flera PowerShell-fönster och köra var och en mot olika instanser samtidigt. Det är något som kan visa sig vara lite tråkigt i flera fall.

Genom att använda detta som ett språngbräda kan du gå lite längre och konfigurera en varningsmekanism för att få meddelanden om varje händelse som upptäcks av skriptet för en fråga som har körts i mer än X antal minuter.


  1. Hitta avståndet mellan två punkter med hjälp av latitud och longitud i mysql

  2. MySQL skapa vy, ersätt vy och släpp vy uttalanden med exempel

  3. Hur ändrar jag mysql till mysqli?

  4. Ta bort HTML-taggar från posten