Database
 sql >> Baza danych >  >> RDS >> Database

Kto jest aktywnym biegaczem

Obecnie w społeczności administratorów baz danych SQL Server jest bardzo prawdopodobne, że używamy lub przynajmniej słyszeliśmy o słynnej procedurze składowanej sp_WhoIsActive opracowany przez Adama Machanica.

Podczas mojej pracy jako DBA używałem SP do natychmiastowego sprawdzania, co dzieje się w konkretnej instancji SQL Server, gdy otrzymuje wszystkie „wskazujące palcem”, że dana aplikacja działa wolno.

Jednak zdarzają się sytuacje, w których takie problemy powracają i wymagają sposobu na uchwycenie tego, co się dzieje, aby znaleźć potencjalnego winowajcę. Istnieją również scenariusze, w których masz kilka instancji służących jako zaplecze dla aplikacji innych firm. Procedura przechowywana może potencjalnie dobrze działać w celu znalezienia naszych winowajców.

W tym artykule przedstawię narzędzie PowerShell, które może pomóc każdemu administratorowi baz danych SQL Server w zbieraniu zapytań wykrytych przez sp_WhoIsActive wewnątrz określonej instancji SQL Server. SP dopasuje je do określonego ciągu wyszukiwania i zapisze je w pliku wyjściowym do późniejszej analizy.

Wstępne rozważania

Oto kilka założeń przed zagłębieniem się w szczegóły skryptu:

  • Skrypt otrzymuje nazwę instancji jako parametr. Jeśli żaden nie zostanie przekazany, localhost zostanie przejęty przez skrypt.
  • Skrypt poprosi o podanie określonego ciągu wyszukiwania w celu porównania go z tekstami zapytań wykonywanych w instancji SQL Server. Jeśli istnieje dopasowanie z którymkolwiek z nich, zostanie ono zapisane w pliku .txt, który możesz później przeanalizować.
  • Plik wyjściowy ze wszystkimi informacjami związanymi z instancją jest generowany dla dokładnej ścieżki, w której znajduje się i uruchamia program PowerShell. Upewnij się, że masz zapis uprawnienia tam.
  • Jeśli wielokrotnie wykonasz skrypt PowerShell dla tego samego wystąpienia, wszystkie wcześniej istniejące pliki wyjściowe zostaną zastąpione. Tylko najnowsza zostanie zachowana. Dlatego jeśli chcesz zachować bardzo konkretny plik, zapisz go w innym miejscu ręcznie.
  • Pakiet zawiera plik .sql plik z kodem do wdrożenia procedury przechowywanej WhoIsActive do głównej bazy danych określonej instancji. Skrypt sprawdza, czy procedura składowana już istnieje w instancji i tworzy ją, jeśli tak nie jest.
    • Możesz zdecydować się na wdrożenie go w innej bazie danych. Po prostu zapewnij niezbędne modyfikacje w skrypcie.
    • Pobierz ten plik .sql plik z bezpiecznego hostingu.
  • Skrypt będzie domyślnie próbował pobrać informacje z instancji SQL Server co 10 sekund. Ale jeśli chcesz użyć innej wartości, dostosuj ją odpowiednio.
  • Upewnij się, że użytkownik zastosowany do nawiązania połączenia z instancją programu SQL Server ma uprawnienia do tworzenia i wykonywania procedur zapisanych w bazie. W przeciwnym razie nie osiągnie swojego celu.

Korzystanie ze skryptu PowerShell

Oto czego możesz oczekiwać od skryptu:

Przejdź do lokalizacji, w której umieściłeś plik skryptu PowerShell i uruchom go w ten sposób:

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

Używam C:\temp jako przykładu

Jedyną rzeczą, o którą zapyta Cię skrypt, jest rodzaj loginu, którego chcesz użyć do połączenia z instancją.

Uwaga:jeśli korzystasz z programu PowerShell ISE, monity będą wyglądać jak zrzuty ekranu. Jeśli uruchomisz go bezpośrednio z konsoli PowerShell, opcje będą wyświetlane jako tekst w tym samym oknie .

Zaufany – połączenie z instancją SQL Server zostanie nawiązane z tym samym użytkownikiem, co do wykonania skryptu PowerShell. Nie musisz podawać żadnych poświadczeń, przyjmie je na podstawie kontekstu.

Logowanie do systemu Windows – w celu poprawnego uwierzytelnienia musisz podać login Windows.

Logowanie SQL – musisz podać login SQL w celu poprawnego uwierzytelnienia.

Bez względu na wybraną opcję, upewnij się, że ma ona wystarczające uprawnienia w instancji do przeprowadzania kontroli .

Jeśli wybierzesz typ logowania, który wymaga wprowadzenia danych uwierzytelniających, skrypt powiadomi Cię w przypadku niepowodzenia:

Po podaniu prawidłowych informacji skrypt sprawdzi, czy SP istnieje w głównej bazie danych i przystąpi do jej tworzenia, jeśli tak nie jest.

Upewnij się, że plik .sql z kodem T-SQL służącym do tworzenia SP znajduje się w tej samej ścieżce, w której znajduje się skrypt. .sql nazwą pliku musi być sp_WhoIsActive.sql .

Jeśli chcesz użyć innej nazwy pliku .sql i innej docelowej bazy danych, wprowadź niezbędne modyfikacje w skrypcie PowerShell:

Następnym krokiem będzie monit o ciąg wyszukiwania . Musisz go wprowadzić, aby zebrać wszelkie dopasowania zwrócone przez każdą iterację wykonania procedury przechowywanej wewnątrz instancji SQL Server.

Następnie musisz wybrać, ile czasu chcesz przeznaczyć na wykonanie skryptu.

W celach demonstracyjnych wybiorę opcję nr 1 (5 min). Zostawię fikcyjną kwerendę uruchomioną w mojej instancji. Zapytanie to POCZEKAJ NA OPÓŹNIENIE '00:10′ . Zamierzam określić ciąg wyszukiwania POCZEKAJ dzięki czemu możesz zorientować się, co skrypt zrobi dla Ciebie.

Po zakończeniu wykonywania skryptu zobaczysz plik .txt plik zawierający nazwę Twojej instancji i WhoIsActive jako przyrostek.

Oto próbka tego, co skrypt przechwycił i zapisał w tym .txt plik:

Pełny kod skryptu PowerShell

Jeśli chcesz wypróbować ten skrypt, użyj poniższego kodu:

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

Wniosek

Pamiętajmy, że WhoIsActive nie przechwytuje zapytań, które są wykonywane bardzo szybko przez silnik DB. Jednak duch tego narzędzia polega na wykrywaniu problematycznych zapytań, które są powolne i mogą skorzystać na rundzie (lub rundach) optymalizacji.

Można argumentować, że śledzenie programu Profiler lub sesja zdarzenia rozszerzonego mogą osiągnąć to samo. Uważam jednak za bardzo wygodne, że możesz po prostu odpalić kilka okien PowerShell i wykonać każde na różnych instancjach w tym samym czasie. To może okazać się trochę nużące w wielu przypadkach.

Używając tego jako odskoczni, możesz pójść nieco dalej i skonfigurować mechanizm ostrzegania, aby otrzymywać powiadomienia o każdym zdarzeniu wykrytym przez skrypt dla dowolnego zapytania, które zostało uruchomione przez ponad X minut.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. SCD typu 4

  2. Jak uzyskać dzień z daty w T-SQL

  3. Jak wyodrębnić podciąg z ciągu w T-SQL

  4. Poprawa rozwiązania mediany górnej/górnej opadającej

  5. Dziesięć najczęstszych zagrożeń dla jakości planu wykonania