Database
 sql >> Datenbank >  >> RDS >> Database

WhoIsActive-Läufer

Heutzutage ist es sehr wahrscheinlich, dass wir innerhalb der SQL Server-DBA-Community die berühmte gespeicherte Prozedur sp_WhoIsActive verwenden oder zumindest davon gehört haben entwickelt von Adam Machanic.

Während meiner Zeit als DBA habe ich den SP verwendet, um sofort zu überprüfen, was in einer bestimmten SQL Server-Instanz vor sich geht, wenn alle mit dem Finger darauf zeigen, dass eine bestimmte Anwendung langsam läuft.

Es gibt jedoch Fälle, in denen solche Probleme wiederkehren und einen Weg erfordern, um zu erfassen, was vor sich geht, um einen potenziellen Schuldigen zu finden. Es gibt auch Szenarien, in denen Sie mehrere Instanzen haben, die als Back-End für Anwendungen von Drittanbietern dienen. Die gespeicherte Prozedur könnte potenziell gut funktionieren, um unsere Schuldigen zu finden.

In diesem Artikel stelle ich ein PowerShell-Tool vor, das jedem SQL Server-DBA helfen kann, Abfragen zu sammeln, die von sp_WhoIsActive erkannt wurden innerhalb einer bestimmten SQL Server-Instanz. Dieser SP würde sie mit einem bestimmten Suchstring abgleichen und sie in einer Ausgabedatei zur Nachanalyse speichern.

Erste Überlegungen

Hier sind einige Annahmen, bevor Sie in die Details des Skripts eintauchen:

  • Das Skript erhält als Parameter den Namen der Instanz. Wenn keine übergeben wird, localhost wird vom Skript übernommen.
  • Das Skript fragt Sie nach einer bestimmten Suchzeichenfolge, um sie mit den Texten von Abfragen zu vergleichen, die in der SQL Server-Instanz ausgeführt werden. Wenn es eine Übereinstimmung mit einem von ihnen gibt, wird sie in einer .txt-Datei gespeichert, die Sie später analysieren können.
  • Die Ausgabedatei mit allen Informationen zu Ihrer Instanz wird für den genauen Pfad generiert, in dem sich die PowerShell befindet und ausgelöst wird. Stellen Sie sicher, dass Sie das Schreiben haben Berechtigungen dort.
  • Wenn Sie das PowerShell-Skript mehrmals für dieselbe Instanz ausführen, werden alle zuvor vorhandenen Ausgabedateien überschrieben. Nur die aktuellste wird beibehalten. Wenn Sie also eine ganz bestimmte Datei aufbewahren müssen, speichern Sie sie manuell an einem anderen Ort.
  • Das Bundle enthält eine .sql Datei mit dem Code zum Bereitstellen der WhoIsActive Stored Procedure in die master-Datenbank der von Ihnen angegebenen Instanz. Das Skript prüft, ob die gespeicherte Prozedur bereits in der Instanz vorhanden ist, und erstellt sie, wenn dies nicht der Fall ist.
    • Sie können es in einer anderen Datenbank bereitstellen. Stellen Sie einfach die notwendigen Änderungen innerhalb des Skripts sicher.
    • Laden Sie diese .sql herunter Datei von sicherem Hosting.
  • Das Skript versucht standardmäßig alle 10 Sekunden, die Informationen von der SQL Server-Instanz abzurufen. Wenn Sie jedoch einen anderen Wert verwenden möchten, passen Sie ihn entsprechend an.
  • Stellen Sie sicher, dass der Benutzer, der für die Verbindung mit der SQL Server-Instanz beantragt wurde, über Berechtigungen zum Erstellen und Ausführen der gespeicherten Prozeduren verfügt. Andernfalls wird es seinen Zweck nicht erfüllen.

Mit dem PowerShell-Skript

Folgendes können Sie von dem Skript erwarten:

Wechseln Sie zu dem Speicherort, an dem Sie die PowerShell-Skriptdatei abgelegt haben, und führen Sie sie wie folgt aus:

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

Ich verwende C:\temp als Beispiel

Das einzige, was das Skript Sie fragen wird, ist die Art der Anmeldung, die Sie verwenden möchten, um sich mit der Instanz zu verbinden.

Hinweis:Wenn Sie PowerShell ISE verwenden, sehen die Eingabeaufforderungen wie Screenshots aus. Wenn Sie es direkt von der PowerShell-Konsole aus ausführen, werden die Optionen als Text im selben Fenster angezeigt .

Vertrauenswürdig – Die Verbindung zur SQL Server-Instanz wird mit demselben Benutzer hergestellt wie für die Ausführung des PowerShell-Skripts. Sie müssen keine Anmeldeinformationen angeben, sie werden basierend auf dem Kontext angenommen.

Windows-Anmeldung – Sie müssen ein Windows-Login für die korrekte Authentifizierung angeben.

SQL-Anmeldung – Sie müssen ein SQL-Login für die korrekte Authentifizierung angeben.

Egal für welche Option Sie sich entscheiden, stellen Sie sicher, dass sie über genügend Berechtigungen in der Instanz verfügt, um Prüfungen durchzuführen .

Wenn Sie den Anmeldetyp wählen, der die Eingabe von Anmeldeinformationen erfordert, benachrichtigt Sie das Skript im Falle eines Fehlers:

Wenn die korrekten Informationen angegeben sind, prüft das Skript, ob der SP in der Master-Datenbank vorhanden ist, und fährt mit der Erstellung fort, wenn dies nicht der Fall ist.

Stellen Sie sicher, dass sich die .sql-Datei mit dem T-SQL-Code zum Erstellen des SP im gleichen Pfad wie das Skript befindet. Die .sql Dateiname muss sp_WhoIsActive.sql sein .

Wenn Sie einen anderen .sql-Dateinamen und eine andere Zieldatenbank verwenden möchten, stellen Sie die erforderlichen Änderungen im PowerShell-Skript sicher:

Der nächste Schritt ist die Suchzeichenfolge . Sie müssen es eingeben, um alle Übereinstimmungen zu sammeln, die von jeder Ausführungsiteration der gespeicherten Prozedur innerhalb der SQL Server-Instanz zurückgegeben werden.

Danach müssen Sie auswählen, wie viel Zeit Sie für die Ausführung des Skripts einplanen möchten.

Zu Demonstrationszwecken wähle ich Option Nr. 1 (5 Minuten). Ich lasse eine Dummy-Abfrage in meiner Instanz laufen. Die Abfrage lautet WAITFOR DELAY ’00:10′ . Ich werde den Suchstring WAITFOR angeben damit Sie ein Gefühl dafür bekommen, was das Skript für Sie tun wird.

Nachdem das Skript seine Ausführung abgeschlossen hat, sehen Sie eine .txt Datei, die den Namen Ihrer Instanz und WhoIsActive enthält als Suffix.

Hier ist ein Beispiel dessen, was das Skript erfasst und in dieser TXT-Datei gespeichert hat Datei:

Vollständiger Code des PowerShell-Skripts

Wenn Sie dieses Skript ausprobieren möchten, verwenden Sie bitte den folgenden 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)"

Schlussfolgerung

Denken wir daran, dass WhoIsActive keine Abfragen erfasst, die sehr schnell von der DB Engine ausgeführt werden. Der Geist dieses Tools besteht jedoch darin, problematische Abfragen zu erkennen, die langsam sind und von einer Optimierungsrunde (oder -runden) profitieren könnten.

Sie könnten argumentieren, dass eine Profiler-Ablaufverfolgung oder eine erweiterte Ereignissitzung dasselbe bewirken könnte. Allerdings finde ich es sehr praktisch, dass man einfach mehrere PowerShell-Fenster starten und jedes gegen verschiedene Instanzen gleichzeitig ausführen kann. Dies könnte sich für mehrere Instanzen als etwas mühsam herausstellen.

Indem Sie dies als Sprungbrett verwenden, könnten Sie noch einen Schritt weiter gehen und einen Warnmechanismus konfigurieren, um über jedes Vorkommen benachrichtigt zu werden, das vom Skript für eine Abfrage erkannt wird, die länger als X Minuten ausgeführt wurde.