我们有几个运行 SQL 代理作业的 SQL Server,在它们自己的 sysjobhistory 表中记录历史记录。我正在尝试设置一个集中式服务器,其中包含一项作业,该作业从所有其他服务器收集历史记录,对其进行一些格式化,然后将其放入名为 AllJobHistory 的表中。作为此过程的一部分,我希望用一列来指示作业的多个步骤是同一作业运行的一部分。它们已通过 job_id 列标记为同一作业的一部分,但我想知道特定行来自作业的 3:00 运行与 4:00 运行。能够基于此列进行过滤将使我们的故障排除工作变得更加容易,但我没有在任何现有系统表或 DMV 中看到任何将这些步骤链接在一起的内容,是吗?
\n\n我自己的第一次尝试是使用 run_date、run_time 和 run_duration 列。对于每个步骤,如果我从 run_time 中减去到目前为止的总 run_duration,它应该使我回到与该作业的所有其他运行相比唯一的时间。看起来它一直在工作,直到我发现它不是 \xe2\x80\x99t 的情况(可能是因为 SQL Server 以秒的精度舍入 run_time 和 run_duration )。这是我对查询的尝试(删除了额外的列)。
\n\nWITH JobDetails AS \n(\n SELECT \n QUOTENAME(UPPER(\'ServerName\')) AS [Server],\n j.job_id AS [JobID],\n j.name AS [JobName],\n s.step_id AS [Step],\n msdb.dbo.agent_datetime(run_date, run_time) AS [RunDate],\n (run_duration/10000*3600 + (run_duration/100)%100*60 + run_duration%100) AS [RunDurationSeconds]\n FROM msdb.dbo.sysjobhistory h\n INNER JOIN msdb.dbo.sysjobs j ON j.job_id = h.job_id\n LEFT OUTER JOIN msdb.dbo.sysjobsteps s ON s.job_id = h.job_id AND s.step_id = h.step_id \n WHERE h.step_id != 0\n), GroupedDetails AS (\n SELECT \n jd.[Server],\n jd.[JobID],\n jd.JobName,\n jd.Step,\n jd.RunDate,\n jd.RunDurationSeconds,\n DATEADD(SECOND, \n -ISNULL(SUM(jd.RunDurationSeconds) OVER\n (PARTITION BY jd.JobName ORDER BY jd.JobName, jd.RunDate, jd.Step \n ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING), 0), \n jd.RunDate) AS grp\n FROM JobDetails AS jd\n)\nSELECT \n gd.[Server],\n gd.JobName,\n gd.Step,\n gd.RunDate,\n gd.RunDurationSeconds,\n CONVERT(VARCHAR(36), gd.JobID) + \'_\' + FORMAT(gd.grp, \'yyyyMMdd_HHmmss\') AS JobRunString\nFROM GroupedDetails AS gd;\n
Run Code Online (Sandbox Code Playgroud)\n\n这是一个示例,它按照我的预期工作,对于一个包含三个步骤的工作。请注意,JobRunString 与第一次运行匹配,并与第二次运行匹配。\n
这是一个例子,它没有按照我想要的方式工作。请注意,Step1RunDate + Step1RunDurationSeconds != Step2RunDate,导致 JobRunString 不匹配。\n
那么,有没有可靠的方法可以将 sysjobhistory 中运行的作业步骤链接在一起?
\n请注意,sysjobhistory
有一个 ID 列 ( instance_id
)。对于已完成的作业的每个步骤,表中应至少有一个条目,后跟step_id
= 0 的条目,记录作业的结果。每个步骤还记录该步骤的启动时间 (run_date
和run_time
),该时间等于或大于作业的启动时间。因此,step_id
给定运行的 = 0 行的运行时间高于 instance_id
相关步骤,但运行时间较低(或相等)。
因此,尝试将数据从 where step_id
= 0 的行初始提取到临时表(或等效表)中。然后,sysjobhistory
具有相同job_id
、较低 instance_id
和较高或相等开始时间(从run_date
和run_time
)的所有行都应该属于您要查找的作业运行。
我在前雇主的一份失败的工作报告中使用了类似的东西。
这是该代码的精简、修改版本。刚才我在 SQL Server 2016 机器上进行了快速测试,它运行正常。但是,我没有任何运行频率足够高的作业,以至于多次运行具有相同的运行时间。
USE msdb;
DECLARE @start_date varchar(8) = '20171001';
IF(OBJECT_ID('tempdb..#failhist') IS NOT NULL) DROP TABLE #failhist;
CREATE TABLE #failhist
(
[job_name] [sysname] NOT NULL,
[run_datetime] [datetime] NULL,
[run_ended] [datetime] NULL,
[instance_id] [int] NOT NULL,
[job_id] [uniqueidentifier] NOT NULL,
[run_date] [int] NOT NULL,
[run_time] [int] NOT NULL
);
INSERT INTO #failhist
select *
FROM
(SELECT j.name as job_name
,CONVERT(DATETIME, RTRIM(run_date)) + (run_time * 9 + run_time % 10000 * 6 + run_time % 100 * 10) / 216e4
as run_datetime
,CONVERT(DATETIME, RTRIM(run_date)) + (run_time * 9 + run_time % 10000 * 6 + run_time % 100 * 10) / 216e4 + (run_duration * 9 + run_duration % 10000 * 6 + run_duration % 100 * 10) / 216e4
as run_ended
,h.instance_id
,h.job_id
,h.run_date
,h.run_time
from msdb..sysjobhistory h INNER JOIN msdb..sysjobs j ON h.job_id = j.job_id
WHERE h.step_id = 0
and h.run_date >= @start_date
) x
;
SELECT
t.job_name AS [Job/Step]
,' ' + CONVERT(varchar(19), t.run_datetime, 121) + ' to ' + CONVERT(varchar(19), t.run_ended, 121) AS Run_Msg
,0 as HdrDetail
,t.job_name
,t.run_datetime
,t.instance_id
,-1 as step_id
,jh.instance_id as actual_instance_id
FROM #failhist t
INNER JOIN msdb..sysjobhistory jh ON ( t.job_id = jh.job_id
AND t.instance_id >= jh.instance_id
AND ( t.run_date < jh.run_date
OR ( t.run_date = jh.run_date
AND t.run_time <= jh.run_time
)
)
)
WHERE jh.step_id = 0
UNION ALL
SELECT
' '
+CASE
WHEN jh.step_id = 0
THEN 'Job Summary'
WHEN jh.run_status BETWEEN 0 AND 1 -- summary
THEN RIGHT(CAST(100 + jh.step_id as varchar),2) + ' - ' + jh.step_name
ELSE ' ' + RIGHT(CAST(100 + jh.step_id as varchar),2) + ' (add''l info)'
END
,' ' + CAST(jh.message as varchar(max))
,1
,t.job_name
,t.run_datetime
,t.instance_id
,jh.step_id
,jh.instance_id
FROM #failhist t
INNER JOIN msdb..sysjobhistory jh ON ( t.job_id = jh.job_id
AND t.instance_id >= jh.instance_id
AND ( t.run_date < jh.run_date
OR ( t.run_date = jh.run_date
AND t.run_time <= jh.run_time
)
)
)
ORDER BY run_datetime, instance_id, step_id
Run Code Online (Sandbox Code Playgroud)
如果您实际上有一个每秒运行一次以上的作业,则可能必须使用窗口函数,以确保您不会从具有相同 run_time 值的作业的早期运行中获取作业步骤。
警告:如果达到每个作业可以包含的行数限制sysjobhistory
,您可能会得到奇怪/不完整的结果。另外,我偶尔会看到作业失败但没有生成作业结果(通常是暂时无法对运行作业的 Windows 用户进行身份验证)。