Ngày nay, trong cộng đồng SQL Server DBA, rất có thể chúng ta đang sử dụng, hoặc ít nhất là đã nghe nói về thủ tục lưu trữ nổi tiếng sp_WhoIsActive được phát triển bởi Adam Machanic.
Trong thời gian làm DBA, tôi đã sử dụng SP để kiểm tra ngay điều gì đang xảy ra bên trong một phiên bản SQL Server cụ thể khi nó nhận được tất cả "chỉ tay" rằng một ứng dụng cụ thể đang chạy chậm.
Tuy nhiên, có những trường hợp các vấn đề như vậy tái diễn và đòi hỏi một cách để nắm bắt những gì đang xảy ra để tìm ra thủ phạm tiềm năng. Cũng có những trường hợp mà bạn có một số trường hợp đóng vai trò là chương trình phụ trợ cho các ứng dụng của 3 bên. Quy trình được lưu trữ có thể hoạt động tốt để tìm ra thủ phạm của chúng tôi.
Trong bài viết này, tôi sẽ trình bày một công cụ PowerShell có thể giúp bất kỳ DBA SQL Server nào thu thập các truy vấn được phát hiện bởi sp_WhoIsActive bên trong một phiên bản SQL Server cụ thể. SP đó sẽ khớp chúng với một Chuỗi tìm kiếm nhất định và lưu trữ chúng trong một tệp đầu ra để phân tích sau.
Cân nhắc ban đầu
Dưới đây là một số giả định trước khi đi sâu vào chi tiết của kịch bản:
- Tập lệnh nhận tên của cá thể làm tham số. Nếu không có gì được thông qua, localhost sẽ do kịch bản đảm nhận.
- Tập lệnh sẽ yêu cầu bạn cung cấp một chuỗi tìm kiếm cụ thể để so sánh nó với văn bản của các truy vấn được thực thi trong phiên bản SQL Server. Nếu trùng khớp với bất kỳ điểm nào trong số chúng, nó sẽ được lưu trữ trong tệp .txt mà bạn có thể phân tích sau.
- Tệp đầu ra với tất cả thông tin liên quan đến phiên bản của bạn được tạo cho đường dẫn chính xác nơi PowerShell được đặt và kích hoạt. Đảm bảo rằng bạn có ghi quyền ở đó.
- Nếu bạn thực thi tập lệnh PowerShell nhiều lần cho cùng một phiên bản, mọi tệp đầu ra hiện có trước đó sẽ bị ghi đè. Chỉ cái gần đây nhất sẽ được giữ lại. Do đó, nếu bạn cần giữ một tệp rất cụ thể, hãy lưu nó ở nơi khác theo cách thủ công.
- Gói bao gồm .sql tệp có mã để triển khai Quy trình lưu trữ WhoIsActive vào cơ sở dữ liệu chính của phiên bản mà bạn chỉ định. Tập lệnh kiểm tra xem thủ tục được lưu trữ đã tồn tại trong trường hợp chưa và tạo nó nếu chưa.
- Bạn có thể chọn triển khai nó tới một cơ sở dữ liệu khác. Chỉ cần đảm bảo các sửa đổi cần thiết trong tập lệnh.
- Tải xuống .sql này tệp từ lưu trữ an toàn.
- Theo mặc định, tập lệnh sẽ cố gắng tìm nạp thông tin từ phiên bản SQL Server 10 giây một lần. Nhưng nếu bạn muốn sử dụng một giá trị khác, hãy điều chỉnh nó cho phù hợp.
- Đảm bảo rằng người dùng được áp dụng để kết nối với phiên bản SQL Server có quyền tạo và thực thi các Thủ tục đã Lưu trữ. Nếu không, nó sẽ không đạt được mục đích của mình.
Sử dụng Tập lệnh PowerShell
Dưới đây là những gì bạn có thể mong đợi từ tập lệnh:
Đi tới vị trí bạn đã đặt tệp tập lệnh PowerShell và chạy nó như sau:
PS C:\temp> .\WhoIsActive-Runner.ps1 SERVER\INSTANCE
Tôi đang sử dụng C:\ temp làm ví dụ
Điều duy nhất mà tập lệnh sẽ hỏi bạn là loại đăng nhập bạn muốn sử dụng để kết nối với phiên bản.
Lưu ý:Nếu bạn sử dụng PowerShell ISE, thì lời nhắc sẽ giống như ảnh chụp màn hình. Nếu bạn chạy nó trực tiếp từ bảng điều khiển PowerShell, thì các tùy chọn sẽ được nhắc dưới dạng văn bản trong cùng một cửa sổ .
Đáng tin cậy - kết nối đến phiên bản SQL Server sẽ được thực hiện với cùng một người dùng như để thực thi tập lệnh PowerShell. Bạn không phải chỉ định bất kỳ thông tin đăng nhập nào, nó sẽ giả định chúng dựa trên ngữ cảnh.
Đăng nhập Windows - bạn phải cung cấp thông tin đăng nhập Windows để xác thực chính xác.
Đăng nhập SQL - bạn phải cung cấp thông tin đăng nhập SQL để xác thực chính xác.
Cho dù bạn chọn tùy chọn nào, hãy đảm bảo rằng tùy chọn đó có đủ đặc quyền trong trường hợp để thực hiện kiểm tra .
Nếu bạn chọn kiểu đăng nhập yêu cầu bạn nhập thông tin đăng nhập, tập lệnh sẽ thông báo cho bạn trong trường hợp không thành công:
Với thông tin chính xác được chỉ định, tập lệnh sẽ kiểm tra xem SP có tồn tại trong cơ sở dữ liệu chính hay không và tiếp tục tạo nếu không có.
Đảm bảo rằng tệp .sql có mã T-SQL để tạo SP nằm trên cùng một đường dẫn nơi chứa tập lệnh. .sql tên tệp phải là sp_WhoIsActive.sql .
Nếu bạn muốn sử dụng tên tệp .sql khác và cơ sở dữ liệu đích khác, hãy đảm bảo các sửa đổi cần thiết bên trong tập lệnh PowerShell:
Bước tiếp theo sẽ là lời nhắc Chuỗi tìm kiếm . Bạn phải nhập nó để thu thập bất kỳ kết quả phù hợp nào được trả về bởi mỗi lần lặp lại thực thi của Thủ tục được lưu trữ bên trong phiên bản SQL Server.
Sau đó, bạn phải chọn khoảng thời gian bạn muốn cho phép để thực thi tập lệnh.
Đối với mục đích trình diễn, tôi sẽ chọn tùy chọn số 1 (5 phút). Tôi sẽ để một truy vấn giả chạy trong trường hợp của tôi. Truy vấn là CHỜ TRÌ HOÃN ’00:10 ′ . Tôi sẽ chỉ định Chuỗi tìm kiếm CHỜ để bạn có thể hiểu tập lệnh sẽ làm gì cho bạn.
Sau khi tập lệnh hoàn tất quá trình thực thi, bạn sẽ thấy .txt tệp chứa tên phiên bản của bạn và WhoIsActive như một hậu tố.
Dưới đây là ví dụ về những gì tập lệnh đã ghi lại và lưu trong .txt đó tệp:
Mã hoàn chỉnh của tập lệnh PowerShell
Nếu bạn muốn thử tập lệnh này, vui lòng sử dụng mã bên dưới:
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)"
Kết luận
Hãy nhớ rằng WhoIsActive sẽ không nắm bắt các truy vấn được DB Engine thực thi rất nhanh. Tuy nhiên, tinh thần của công cụ này là phát hiện những truy vấn có vấn đề chậm và có thể được lợi từ một vòng (hoặc các vòng) tối ưu hóa.
Bạn có thể tranh luận rằng theo dõi Hồ sơ hoặc một phiên Sự kiện mở rộng có thể đạt được điều tương tự. Tuy nhiên, tôi thấy rất tiện lợi là bạn có thể đơn giản kích hoạt một số cửa sổ PowerShell và thực thi từng cửa sổ đối với các trường hợp khác nhau cùng một lúc. Đó là một cái gì đó có thể trở nên hơi tẻ nhạt đối với nhiều trường hợp.
Bằng cách sử dụng điều này làm bước đệm, bạn có thể tiến xa hơn một chút và định cấu hình cơ chế cảnh báo để nhận thông báo về bất kỳ sự cố nào được tập lệnh phát hiện đối với bất kỳ truy vấn nào đã chạy trong hơn X khoảng phút.