sql >> Database >  >> RDS >> Database

WhoIsActive Runner

Tegenwoordig is het binnen de SQL Server DBA-gemeenschap zeer waarschijnlijk dat we de beroemde opgeslagen procedure sp_WhoIsActive gebruiken, of er op zijn minst van hebben gehoord. ontwikkeld door Adam Machanic.

Tijdens mijn tijd als DBA heb ik de SP gebruikt om onmiddellijk te controleren wat er gebeurt in een bepaalde SQL Server-instantie wanneer deze alle "vingeraanwijzingen" krijgt dat een bepaalde toepassing traag werkt.

Er zijn echter gevallen waarin dergelijke problemen terugkeren en een manier vereisen om vast te leggen wat er aan de hand is om een ​​mogelijke boosdoener te vinden. Er zijn ook scenario's waarin u meerdere instanties hebt die als backend dienen voor toepassingen van derden. De Opgeslagen Procedure zou mogelijk goed kunnen werken om onze boosdoeners te vinden.

In dit artikel zal ik een PowerShell-tool presenteren die elke SQL Server-DBA kan helpen bij het verzamelen van query's die zijn gedetecteerd door sp_WhoIsActive binnen een bepaalde SQL Server-instantie. Die SP zou ze matchen met een bepaalde zoekreeks en ze opslaan in een uitvoerbestand voor post-analyse.

Eerste overwegingen

Hier zijn enkele aannames voordat we in de details van het script duiken:

  • Het script ontvangt de naam van de instantie als parameter. Als er geen wordt doorgegeven, localhost wordt overgenomen door het script.
  • Het script zal u om een ​​bepaalde zoekreeks vragen om deze te vergelijken met de teksten van query's die worden uitgevoerd in de SQL Server-instantie. Als er een overeenkomst met een van hen is, wordt deze opgeslagen in een .txt-bestand dat u later kunt analyseren.
  • Het uitvoerbestand met alle informatie met betrekking tot uw exemplaar wordt gegenereerd voor het exacte pad waar de PowerShell zich bevindt en wordt geactiveerd. Zorg ervoor dat u de schrijf . hebt machtigingen daar.
  • Als u het PowerShell-script meerdere keren uitvoert voor dezelfde instantie, worden alle eerder bestaande uitvoerbestanden overschreven. Alleen de meest recente wordt bewaard. Daarom, als je een heel specifiek bestand moet bewaren, sla het dan handmatig ergens anders op.
  • De bundel bevat een .sql bestand met de code om de WhoIsActive Stored Procedure . te implementeren naar de hoofddatabase van het exemplaar dat u opgeeft. Het script controleert of de opgeslagen procedure al bestaat in de instantie en maakt deze aan als dat niet het geval is.
    • U kunt ervoor kiezen om het in een andere database te implementeren. Zorg gewoon voor de nodige wijzigingen in het script.
    • Download deze .sql bestand van veilige hosting.
  • Het script probeert standaard elke 10 seconden de informatie op te halen van de SQL Server-instantie. Maar als u een andere waarde wilt gebruiken, past u deze dienovereenkomstig aan.
  • Zorg ervoor dat de gebruiker die heeft aangevraagd om verbinding te maken met de SQL Server-instantie, machtigingen heeft om de opgeslagen procedures te maken en uit te voeren. Anders zal het zijn doel niet bereiken.

Het PowerShell-script gebruiken

Dit is wat je van het script kunt verwachten:

Ga naar de locatie waar je het PowerShell-scriptbestand hebt geplaatst en voer het als volgt uit:

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

Ik gebruik C:\temp als voorbeeld

Het enige dat het script u vraagt, is het type login dat u wilt gebruiken om verbinding te maken met de instantie.

Opmerking:als u PowerShell ISE gebruikt, zien de prompts eruit als schermafbeeldingen. Als u het rechtstreeks vanuit de PowerShell-console uitvoert, worden de opties als tekst in hetzelfde venster gevraagd .

Vertrouwd – de verbinding met de SQL Server-instantie wordt gemaakt met dezelfde gebruiker als voor de uitvoering van het PowerShell-script. U hoeft geen inloggegevens op te geven, het gaat ervan uit op basis van de context.

Windows-aanmelding – u moet een Windows-login opgeven voor de juiste authenticatie.

SQL-aanmelding – u moet een SQL-login opgeven voor de juiste authenticatie.

Welke optie u ook kiest, zorg ervoor dat deze voldoende rechten heeft in de instantie om controles uit te voeren .

Als u het aanmeldingstype kiest waarvoor u inloggegevens moet invoeren, zal het script u op de hoogte stellen in geval van een fout:

Als de juiste informatie is opgegeven, controleert het script of de SP in de hoofddatabase bestaat en gaat het verder met het maken ervan als dat niet het geval is.

Zorg ervoor dat het .sql-bestand met de T-SQL-code om de SP te maken zich op hetzelfde pad bevindt als waar het script zich bevindt. De .sql bestandsnaam moet sp_WhoIsActive.sql . zijn .

Als u een andere .sql-bestandsnaam en een andere doeldatabase wilt gebruiken, zorg dan voor de nodige wijzigingen in het PowerShell-script:

De volgende stap is de Zoekreeksprompt . U moet het invoeren om eventuele overeenkomsten te verzamelen die worden geretourneerd door elke uitvoeringsiteratie van de opgeslagen procedure in de SQL Server-instantie.

Daarna moet je kiezen hoeveel tijd je wilt toestaan ​​voor de uitvoering van het script.

Voor demonstratiedoeleinden kies ik optie #1 (5 minuten). Ik zal een dummy-query in mijn instantie laten lopen. De vraag is WAITFOR DELAY '00:10′ . Ik ga de zoekreeks WAITFOR . specificeren zodat u een idee krijgt van wat het script voor u zal doen.

Nadat het script de uitvoering heeft voltooid, ziet u een .txt bestand dat de naam van uw instantie en WhoIsActive . bevat als achtervoegsel.

Hier is een voorbeeld van wat het script heeft vastgelegd en opgeslagen in die .txt bestand:

Volledige code van het PowerShell-script

Als je dit script wilt proberen, gebruik dan de onderstaande code:

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)"

Conclusie

Laten we in gedachten houden dat WhoIsActive geen query's vastlegt die erg snel worden uitgevoerd door de DB Engine. De geest van deze tool is echter om die problematische zoekopdrachten te detecteren die traag zijn en zouden kunnen profiteren van een optimalisatieronde (of -rondes).

Je zou kunnen stellen dat een Profiler-tracering of een Extended Event-sessie hetzelfde zou kunnen bereiken. Ik vind het echter erg handig dat je eenvoudig meerdere PowerShell-vensters kunt starten en elk tegelijkertijd op verschillende instanties kunt uitvoeren. Het is iets dat in meerdere gevallen een beetje vervelend kan blijken te zijn.

Door dit als een springplank te gebruiken, kunt u een beetje verder gaan en een waarschuwingsmechanisme configureren om een ​​melding te krijgen over elk voorval dat door het script wordt gedetecteerd voor elke zoekopdracht die al meer dan X minuten loopt.


  1. Tellen gebruiken om het aantal keren te vinden

  2. Prestatievoordelen vinden met partitionering

  3. Oracle High Availability-concepten in PostgreSQL

  4. Querysnelheid verbeteren:eenvoudige SELECT in grote postgres-tabel