V dnešní době v rámci komunity SQL Server DBA je velmi pravděpodobné, že používáme, nebo jsme o ní alespoň slyšeli, slavnou uloženou proceduru sp_WhoIsActive vyvinul Adam Machanic.
Během svého působení jako DBA jsem používal SP k okamžité kontrole toho, co se děje v konkrétní instanci SQL Serveru, když dostává veškeré „ukazování prstů“, že konkrétní aplikace běží pomalu.
Existují však případy, kdy se takové problémy opakují a vyžadují způsob, jak zachytit, co se děje, a najít potenciálního viníka. Existují také scénáře, kde máte několik instancí sloužících jako backend pro aplikace 3 stran. Uložená procedura by mohla potenciálně dobře fungovat při hledání našich viníků.
V tomto článku představím nástroj PowerShell, který může pomoci libovolnému SQL Server DBA shromažďovat dotazy zjištěné sp_WhoIsActive uvnitř konkrétní instance SQL Server. Tento SP je přiřadí k určitému vyhledávacímu řetězci a uloží je do výstupního souboru pro následnou analýzu.
Počáteční úvahy
Zde jsou některé předpoklady, než se ponoříme do detailů skriptu:
- Skript obdrží název instance jako parametr. Pokud není předán žádný, localhost bude převzat skriptem.
- Skript vás požádá o konkrétní vyhledávací řetězec, aby jej porovnal s texty dotazů prováděných v instanci SQL Server. Pokud existuje shoda s některým z nich, bude uložena v souboru .txt, který můžete později analyzovat.
- Výstupní soubor se všemi informacemi souvisejícími s vaší instancí se vygeneruje pro přesnou cestu, kde se PowerShell nachází a spouští. Ujistěte se, že máte zápis tam oprávnění.
- Pokud skript PowerShell spustíte vícekrát pro stejnou instanci, všechny dříve existující výstupní soubory budou přepsány. Zachována bude pouze ta nejnovější. Pokud si tedy potřebujete ponechat velmi specifický soubor, uložte jej někde jinde ručně.
- Balík obsahuje .sql soubor s kódem pro nasazení uložené procedury WhoIsActive do hlavní databáze instance, kterou určíte. Skript zkontroluje, zda uložená procedura již v instanci existuje, a pokud ne, vytvoří ji.
- Můžete jej nasadit do jiné databáze. Stačí zajistit potřebné úpravy ve skriptu.
- Stáhněte si tento .sql soubor z bezpečného hostingu.
- Skript se ve výchozím nastavení pokusí načíst informace z instance SQL Server každých 10 sekund. Pokud ale chcete použít jinou hodnotu, upravte ji podle toho.
- Ujistěte se, že uživatel, který se použije k připojení k instanci SQL Server, má oprávnění k vytváření a spouštění uložených procedur. Jinak nesplní svůj účel.
Použití skriptu PowerShell
Zde je to, co můžete od skriptu očekávat:
Přejděte do umístění, kam jste umístili soubor skriptu PowerShell, a spusťte jej takto:
PS C:\temp> .\WhoIsActive-Runner.ps1 SERVER\INSTANCE
Jako příklad používám C:\temp
Jediná věc, na kterou se vás skript zeptá, je typ přihlášení, který chcete použít pro připojení k instanci.
Poznámka:Pokud používáte PowerShell ISE, budou výzvy vypadat jako snímky obrazovky. Pokud jej spustíte přímo z konzoly PowerShell, možnosti budou vyzvány jako text ve stejném okně .
Důvěryhodný – připojení k instanci SQL Serveru bude provedeno se stejným uživatelem jako při provádění skriptu PowerShell. Nemusíte zadávat žádné přihlašovací údaje, bude je předpokládat na základě kontextu.
Přihlášení do systému Windows – pro správné ověření musíte zadat přihlašovací jméno do systému Windows.
Přihlášení k SQL – pro správnou autentizaci musíte zadat přihlašovací jméno SQL.
Bez ohledu na to, kterou možnost zvolíte, ujistěte se, že má v instanci dostatek oprávnění k provádění kontrol .
Pokud zvolíte typ přihlášení, který vyžaduje zadání přihlašovacích údajů, skript vás v případě selhání upozorní:
Se správně zadanými informacemi skript zkontroluje, zda SP existuje v hlavní databázi, a pokud ne, pokračuje v jeho vytvoření.
Ujistěte se, že soubor .sql s kódem T-SQL pro vytvoření SP je umístěn na stejné cestě, kde je umístěn skript. Soubor .sql název souboru musí být sp_WhoIsActive.sql .
Pokud chcete použít jiný název souboru .sql a jinou cílovou databázi, zajistěte potřebné úpravy ve skriptu PowerShell:
Dalším krokem bude výzva Vyhledat řetězec . Musíte jej zadat, abyste shromáždili všechny shody vrácené každou iterací provedení uložené procedury uvnitř instance SQL Server.
Poté musíte vybrat, kolik času chcete ponechat na provedení skriptu.
Pro demonstrační účely zvolím možnost #1 (5 minut). V mé instanci nechám spuštěný fiktivní dotaz. Dotaz je WAITFOR DELAY ’00:10′ . Uvedu vyhledávací řetězec WAITFOR abyste mohli získat představu o tom, co pro vás skript udělá.
Po dokončení skriptu se zobrazí .txt soubor, který obsahuje název vaší instance a WhoIsActive jako příponu.
Zde je ukázka toho, co skript zachytil a uložil do tohoto .txt soubor:
Úplný kód skriptu PowerShell
Pokud chcete tento skript vyzkoušet, použijte níže uvedený kód:
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)"
Závěr
Mějme na paměti, že WhoIsActive nezachytí dotazy, které DB Engine spouští velmi rychle. Podstatou tohoto nástroje je však odhalit ty problematické dotazy, které jsou pomalé a mohly by mít prospěch z optimalizačního kola (nebo kol).
Můžete namítnout, že trasování Profileru nebo relace Extended Event by mohly dosáhnout stejné věci. Považuji však za velmi pohodlné, že můžete jednoduše spustit několik oken PowerShellu a spustit každé z různých instancí současně. Je to něco, co by se v mnoha případech mohlo ukázat jako trochu únavné.
Když to použijete jako odrazový můstek, můžete jít o něco dále a nakonfigurovat mechanismus upozornění, abyste byli informováni o každém výskytu detekovaném skriptem pro jakýkoli dotaz, který byl spuštěn déle než X minut.