現在、SQL Server DBAコミュニティ内では、有名なストアドプロシージャ sp_WhoIsActive を使用するか、少なくとも聞いたことがある可能性が非常に高いです。 アダムマハニックによって開発されました。
DBAとして働いていたとき、SPを使用して、特定のアプリケーションの実行速度が遅いというすべての「指差し」を取得したときに、特定のSQLServerインスタンス内で何が起こっているかをすぐに確認しました。
ただし、そのような問題が繰り返し発生し、潜在的な原因を見つけるために何が起こっているのかを把握する方法が必要になる場合があります。サードパーティアプリケーションのバックエンドとして機能する複数のインスタンスがあるシナリオもあります。ストアドプロシージャは、原因を見つけるのに役立つ可能性があります。
この記事では、SQLServerDBAがsp_WhoIsActiveによって検出されたクエリを収集するのに役立つPowerShellツールを紹介します。 特定のSQLServerインスタンス内。そのSPは、それらを特定の検索文字列と照合し、事後分析のために出力ファイルに保存します。
最初の考慮事項
スクリプトの詳細に飛び込む前に、いくつかの前提条件を次に示します。
- スクリプトは、インスタンスの名前をパラメーターとして受け取ります。何も渡されない場合は、 localhost スクリプトによって想定されます。
- スクリプトは、SQLServerインスタンスで実行されたクエリのテキストと比較するために特定の検索文字列を要求します。それらのいずれかと一致する場合は、後で分析できる.txtファイルに保存されます。
- インスタンスに関連するすべての情報を含む出力ファイルは、PowerShellが配置されてトリガーされる正確なパスに対して生成されます。 書き込みがあることを確認してください そこに許可があります。
- 同じインスタンスに対してPowerShellスクリプトを複数回実行すると、既存の出力ファイルが上書きされます。最新のもののみが保持されます。したがって、非常に具体的なファイルを保持する必要がある場合は、手動で別の場所に保存してください。
- バンドルには.sqlが含まれています WhoIsActive Stored Procedureをデプロイするためのコードを含むファイル 指定したインスタンスのマスターデータベースに移動します。スクリプトは、ストアドプロシージャがインスタンスにすでに存在するかどうかを確認し、存在しない場合は作成します。
- 別のデータベースにデプロイすることを選択できます。スクリプト内で必要な変更を確認してください。
- この.sqlをダウンロードします 安全なホスティングからのファイル。
- スクリプトは、デフォルトで10秒ごとにSQLServerインスタンスから情報を取得しようとします。ただし、別の値を使用する場合は、それに応じて調整してください。
- 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がマスターデータベースに存在するかどうかを確認し、存在しない場合は作成に進みます。
SPを作成するためのT-SQLコードを含む.sqlファイルが、スクリプトが配置されているのと同じパスに配置されていることを確認してください。 .sql ファイル名はsp_WhoIsActive.sqlである必要があります 。
別の.sqlファイル名と別のターゲットデータベースを使用する場合は、PowerShellスクリプト内で必要な変更を行ってください。
次のステップは、検索文字列プロンプトです。 。 SQL Serverインスタンス内のストアドプロシージャの各実行反復によって返される一致を収集するには、これを入力する必要があります。
その後、スクリプトの実行に許可する時間を選択する必要があります。
デモンストレーションの目的で、オプション#1(5分)を選択します。インスタンスでダミークエリを実行したままにします。クエリはWAITFORDELAY ’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エンジンによって非常に高速に実行されるクエリをキャプチャしないことに注意してください。ただし、このツールの精神は、速度が遅く、最適化ラウンド(または複数のラウンド)の恩恵を受ける可能性のある問題のあるクエリを検出することです。
プロファイラートレースまたは拡張イベントセッションで同じことができると主張するかもしれません。ただし、複数のPowerShellウィンドウを起動して、それぞれを異なるインスタンスに対して同時に実行できると非常に便利です。これは、複数のインスタンスにとって少し面倒であることが判明する可能性があります。
これを足がかりとして使用することで、もう少し進んでアラートメカニズムを構成し、X分を超えて実行されたクエリのスクリプトによって検出された発生について通知を受け取ることができます。