Database
 sql >> база данни >  >> RDS >> Database

WhoIsActive Runner

В днешно време в рамките на DBA общността на SQL Server е изключително вероятно да използваме или поне да сме чували за известната съхранена процедура sp_WhoIsActive разработено от Адам Мачаник.

По време на времето си като DBA използвах SP, за да проверя незабавно какво се случва в конкретен екземпляр на SQL Server, когато получава всички „посочване с пръст“, че дадено приложение работи бавно.

Въпреки това, има случаи, когато подобни проблеми се повтарят и изискват начин за улавяне на случващото се, за да се намери потенциален виновник. Има и сценарии, при които имате няколко екземпляра, служещи като бекенд за приложения на 3 страни. Съхранената процедура може да работи потенциално добре, за да открие нашите виновници.

В тази статия ще представя инструмент PowerShell, който може да помогне на всеки SQL Server DBA да събира заявки, открити от sp_WhoIsActive вътре в конкретен екземпляр на SQL Server. Този SP ще ги съпостави с определен низ за търсене и ще ги съхрани в изходен файл за последващ анализ.

Първоначални съображения

Ето някои предположения, преди да се потопите в детайлите на скрипта:

  • Скриптът получава името на екземпляра като параметър. Ако нито едно не е предадено, localhost ще бъде поето от скрипта.
  • Скриптът ще поиска от вас конкретен низ за търсене, за да го сравните с текстовете на заявките, изпълнени в екземпляра на SQL Server. Ако има съвпадение с някой от тях, то ще бъде съхранено в .txt файл, който можете да анализирате по-късно.
  • Изходният файл с цялата информация, свързана с вашия екземпляр, се генерира за точния път, където се намира и задейства PowerShell. Уверете се, че имате запис разрешения там.
  • Ако изпълните скрипта PowerShell няколко пъти за един и същи екземпляр, всички съществуващи по-рано изходни файлове ще бъдат презаписани. Ще бъде запазен само последният. Ето защо, ако трябва да запазите много конкретен файл, запазете го някъде другаде ръчно.
  • Пакетът включва .sql файл с кода за внедряване на WhoIsActive Stored Procedure към главната база данни на посочения от вас екземпляр. Скриптът проверява дали съхранената процедура вече съществува в екземпляра и го създава, ако не го прави.
    • Можете да изберете да го разположите в друга база данни. Просто осигурете необходимите промени в скрипта.
    • Изтеглете този .sql файл от безопасен хостинг.
  • По подразбиране скриптът ще се опитва да извлече информацията от екземпляра на SQL Server на всеки 10 секунди. Но ако искате да използвате различна стойност, коригирайте я съответно.
  • Уверете се, че потребителят, кандидатстван за свързване към екземпляра на SQL Server, има разрешения за създаване и изпълнение на Съхранените процедури. В противен случай няма да успее да изпълни целта си.

Използване на скрипта PowerShell

Ето какво можете да очаквате от скрипта:

Отидете на мястото, където сте поставили скриптния файл на PowerShell, и го стартирайте по следния начин:

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

Използвам C:\temp като пример

Единственото нещо, което скриптът ще ви попита, е типа на входа, който искате да използвате, за да се свържете с инстанцията.

Забележка:Ако използвате PowerShell ISE, подканите ще изглеждат като екранни снимки. Ако го стартирате директно от конзолата PowerShell, тогава опциите ще бъдат подканени като текст в същия прозорец .

Доверен – връзката към екземпляра на SQL Server ще бъде направена със същия потребител като при изпълнението на скрипта PowerShell. Не е нужно да посочвате никакви идентификационни данни, той ще ги приеме въз основа на контекста.

Вход в Windows – трябва да предоставите данни за вход в Windows за правилното удостоверяване.

Вход в SQL – трябва да предоставите SQL данни за вход за правилното удостоверяване.

Без значение коя опция изберете, уверете се, че има достатъчно привилегии в екземпляра за извършване на проверки .

Ако изберете типа вход, който изисква да въведете идентификационни данни, скриптът ще ви уведоми в случай на неуспех:

С посочената правилна информация скриптът ще провери дали SP съществува в главната база данни и ще продължи да го създаде, ако не съществува.

Уверете се, че .sql файлът с T-SQL кода за създаване на SP се намира на същия път, където се намира скриптът. .sql името на файла трябва да е sp_WhoIsActive.sql .

Ако искате да използвате различно име на .sql файл и различна целева база данни, осигурете необходимите модификации в скрипта PowerShell:

Следващата стъпка ще бъде подканата за низ за търсене . Трябва да го въведете, за да съберете съвпаденията, върнати от всяка итерация на изпълнение на Съхранената процедура в екземпляра на SQL Server.

След това трябва да изберете колко време искате да отделите за изпълнение на скрипта.

За демонстрационни цели ще избера опция №1 (5 минути). Ще оставя фиктивна заявка да работи в моя екземпляр. Заявката е WAITFOR DELAY ’00:10′ . Ще посоча низа за търсене WAITFOR за да можете да получите представа какво ще направи скриптът за вас.

След като скриптът завърши изпълнението си, ще видите .txt файл, който съдържа името на вашия екземпляр и WhoIsActive като суфикс.

Ето пример за това, което скриптът улови и запази в този .txt файл:

Пълен код на скрипта PowerShell

Ако искате да изпробвате този скрипт, моля, използвайте кода по-долу:

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

Заключение

Нека имаме предвид, че WhoIsActive няма да улавя заявки, които се изпълняват много бързо от DB Engine. Въпреки това духът на този инструмент е да открие онези проблемни заявки, които са бавни и биха могли да се възползват от оптимизационен кръг (или кръгове).

Може да възразите, че проследяване на Profiler или сесия на разширено събитие биха могли да постигнат същото. Въпреки това намирам за много удобно, че можете просто да стартирате няколко прозореца на PowerShell и да изпълните всеки срещу различни екземпляри едновременно. Това е нещо, което може да се окаже малко досадно за множество случаи.

Като използвате това като стъпка, можете да отидете малко по-далеч и да конфигурирате механизъм за предупреждение, за да получавате известия за всяко събитие, открито от скрипта, за всяка заявка, която се изпълнява повече от X минути.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. SQL команди

  2. NoSQL:живот без схема

  3. Прочетете изолацията на ангажирани моментни снимки

  4. Проблеми с представянето:Първата среща

  5. ScaleGrid DBaaS в краткия списък за наградите Cloud Excellence Awards 2018