PowerShell Usage in DBA Work — Case Study 4/7

One common surprise I have encountered in almost all my DBA environments is that a sql job that usually runs <x> minutes, sometimes can take <2x> minutes or even more time to finish.

Case Scenario: (Monitor / alert long running sql jobs)

We have hundreds of sql instances supporting many 3rd party applications, and there are many sql jobs that are part of these applications, all the jobs are supposed to finish within a few seconds to a few minutes, some may take longer up to about 2 hours. But no job should take longer than 8 hours. However, from time to time, we do see jobs running the whole weekend and still not  finishing on Monday morning and start to impact users. Usually we do not know this until users start to complain.

So from proactive monitoring perspective, how can we monitor these jobs and alert the application support teams if the job is running longer than 8 hours?

PowerShell Solution:

The solution logic is very straight-forward, using sp_help_job @execution_status = 1 to get the list of jobs running. and then get the job start information from msdb.dbo.sysjobactivity.

## Tested against sql2005+, PS V2
[int]$min_ago = 10 ## if job has been running longer than $min_ago, we will capture it

[string]$sqlInstance = "MyServer"

$jobSet = invoke-sqlcmd -Server $sqlInstance -Query "exec msdb.dbo.sp_help_job @execution_status=1" -Database "msdb";


[string]$query = @"
set nocount on;
declare @session_id int;
select @session_id = max(session_id) from msdb.dbo.syssessions;

select JobName = j.name
, JobStartTime=a.start_execution_date
, Duration_Min=datediff(minute, a.start_execution_date, getdate())
from dbo.sysjobactivity a
inner join dbo.sysjobs j
on j.job_id = a.job_id
and j.name in (`$(Jobs))
and a.stop_execution_date is null
and a.session_id = @session_id
and a.start_execution_date < dateadd(minute, $min_ago, getdate());

if ($jobSet -ne $null)
$jobSet | ForEach-Object -Begin {[string] $jobname_list="";} -Process { $jobname_list += "'" + $_.name + "'" +","} -End {$jobname_list = $jobname_list.substring(0,$jobname_list.Length-1);}

[string[]]$var = "Jobs=$jobname_list";

invoke-sqlcmd -Server $sqlInstance-Query $query -database "msdb" -variable $var | select JobName, JobStartTime, Duration_Min | ft -auto -Wrap

Advantage Analysis:

When I initially thought of this case, I thought it would be pretty easy by first creating a proper temp table, and then

insert into #tmp_table exec msdb.dbo.sp_help_job @execution_status = 1;

Then I can join this #tmp_table with msdb.dbo.sysjobactivity to find out the jobs in question. However, when I run the t-sql, I will get the following error

Msg 8164, Level 16, State 1, Procedure sp_get_composite_job_info, Line 72
An INSERT EXEC statement cannot be nested.

However, using PS, I avoid this error. In my real monitoring script, an alert with all the necessary info will be sent out via email to DBAs once a long-running job is detected.

Other Thoughts:

There are a few times that pure t-sql solution is not ideal or efficient. The above mentioned issue is a one. There is another one, say, I have a variable $tbl, and during my business process, this $tbl may have different value pointing to the real table names, and I want to be able to do the following:

select * from $tbl

This is not possible in current t-sql (except for dynamic t-sql which will make the script very “spaghetti”). Yet with PS, this is easy to achieve by setting the correct –variable parameter in the invoke-sqlcmd cmdlet.

This entry was posted in Administration and tagged . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )


Connecting to %s