我们有几个 SQL Server 运行 SQL 代理作业,在它们自己的 sysjobhistory 表中记录历史。我正在尝试设置一个集中式服务器,其作业收集所有其他服务器的历史记录,对其进行格式化,然后将其放入名为 AllJobHistory 的表中。作为此过程的一部分,我希望在一个列中表明作业的多个步骤是同一作业运行的一部分。它们已通过 job_id 列标记为同一作业的一部分,但我想知道特定行来自作业的 3:00 运行与 4:00 运行。能够基于此列进行过滤将使我们的故障排除工作变得更加容易,但我看不到任何现有系统表或 DMV 中将这些步骤链接在一起的任何内容,是吗?
我自己的第一次尝试是使用 run_date、run_time 和 run_duration 列。对于每一步,如果我从 run_time 中减去到目前为止的总 run_duration,它应该让我回到与该作业的所有其他运行相比独一无二的时间。看起来它一直在工作,直到我发现它不是(可能是因为 SQL Server 以秒为精度四舍五入 run_time 和 run_duration)。这是我对查询的尝试(删除了额外的列)。
WITH JobDetails AS
(
SELECT
QUOTENAME(UPPER('ServerName')) AS [Server],
j.job_id AS [JobID],
j.name AS [JobName],
s.step_id AS [Step],
msdb.dbo.agent_datetime(run_date, run_time) AS [RunDate],
(run_duration/10000*3600 + (run_duration/100)%100*60 + run_duration%100) AS [RunDurationSeconds]
FROM msdb.dbo.sysjobhistory h
INNER JOIN msdb.dbo.sysjobs j ON j.job_id = h.job_id
LEFT OUTER JOIN msdb.dbo.sysjobsteps s ON s.job_id = h.job_id AND s.step_id = h.step_id
WHERE h.step_id != 0
), GroupedDetails AS (
SELECT
jd.[Server],
jd.[JobID],
jd.JobName,
jd.Step,
jd.RunDate,
jd.RunDurationSeconds,
DATEADD(SECOND,
-ISNULL(SUM(jd.RunDurationSeconds) OVER
(PARTITION BY jd.JobName ORDER BY jd.JobName, jd.RunDate, jd.Step
ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING), 0),
jd.RunDate) AS grp
FROM JobDetails AS jd
)
SELECT
gd.[Server],
gd.JobName,
gd.Step,
gd.RunDate,
gd.RunDurationSeconds,
CONVERT(VARCHAR(36), gd.JobID) + '_' + FORMAT(gd.grp, 'yyyyMMdd_HHmmss') AS JobRunString
FROM GroupedDetails AS gd;
这是一个示例,它按我的意愿工作,对于一个包含三个步骤的工作。请注意,JobRunString 匹配第一次运行和第二次运行。
这是一个示例,它没有按我的意愿工作。注意 Step1RunDate + Step1RunDurationSeconds != Step2RunDate,导致 JobRunString 不匹配。
那么,是否有任何可靠的方法可以将 sysjobhistory 中运行的作业步骤链接在一起?
请注意,它
sysjobhistory
有一个 ID 列 (instance_id
)。对于已完成的作业的每个步骤,至少应该有一个条目,然后是一个step_id
= 0 的条目,记录作业的结果。每个步骤还记录步骤开始的时间 (run_date
和run_time
),这将等于或大于作业开始的时间。因此,step_id
给定运行的 = 0 行的运行时间高于instance_id
相关步骤,但运行时间较短(或相等)。因此,请尝试将 = 0 的行中的数据初始提取
step_id
到临时表(或等效表)中。sysjobhistory
然后,具有相同job_id
、较低instance_id
和较高或相等开始时间(从run_date
和)的所有行都run_time
应该属于您正在寻找的作业运行。我曾经在前雇主的一份失败的工作报告中使用过类似的东西。
这是该代码的精简修改版本。刚才我在 SQL Server 2016 机器上进行了快速测试。但是,我没有任何运行频率足够高以至于多次运行具有相同运行时间的作业。
如果您实际上有一个每秒运行一次以上的作业,您可能必须使用窗口函数,以确保您不会从具有相同 run_time 值的作业的早期运行中选择作业步骤。
警告:如果达到每个作业可以包含的行数限制
sysjobhistory
,您可能会得到奇怪/不完整的结果。此外,我偶尔会看到作业失败而没有生成作业结果(通常是暂时无法对运行作业的 Windows 用户进行身份验证)。感谢@RDFozz 提供的信息,我能够提出一个查询来获取我正在寻找的所有数据。我不想包含第 0 步“工作结果”行,并且我还删除了 @RDFozz 查询中的漂亮格式。这对于报告来说很棒,但我希望它更像是一个关系表。
为了将许多服务器的结果编译到一个服务器上,然后我执行以下操作以获得每个作业运行的数字,该数字在服务器之间是唯一的(dbo.DBA_AllJobHistory_JobRun 是一个Sequence):
然后这会将来自该服务器的历史记录插入到组合多个服务器的 AllJobHistory 表中。
这是我的解决方案:(我的解决方案还提取了 execution_id: 用于从 SQL Server 作业执行的 SSIS。我在其他地方得到了部分查询,我忘记了在哪里)。
我花了很多时间思考如何才能获得工作所需的内容,并尝试了不同的方法,例如使用 SysJobActivity 表来帮助我选择相关的步骤,但这不是一种可靠的方法。我想要每次跑步,而不仅仅是最近一次跑步。
我最终注意到,对于每次运行,总是有一个 step_id = 0,即使它在运行 step_id = 1 之前失败,并且那个 step_id 是最后写入的。因此,当搜索 step_order = 0 的给定 job_id 时,您可以计算出在 step_order = 0 相同作业的上一次运行和当前作业的 step_order = 0 运行之间运行的所有作业步骤。
我从其他人那里得到了一些想法,它们并不都是我自己的。但是现在我看到我在这个堆栈交换帖子中拥有的东西与其他人有一些相似之处。但我的也不一样。现在对我来说效果很好。
我在这里的工作是主要的内部和外部联接以及将 prev_instance_id 与外部查询联接的方案,然后在最外部的查询中确定哪些子行与哪些父行一起使用 (step_id = 0)。
在我重写查询之前我有一些代码,可能昨天完成了。我曾经拥有的那个代码是一个 hack,它不可靠并且存在问题。我希望 Microsoft 使 SQL Server 作业代理作业表更易于理解。他们应该提供 parent_instance_id 或类似的东西来链接它们或其他一些简单的机制。但我在上面的查询中创建了这个。您可以非常轻松地将其转换为视图或函数或存储过程,以使其灵活且可重用!我有一个视图,稍后会被存储过程调用!
我使用上面的示例和 CTE 创建了一个查询来拉回所有存在的执行历史记录,并将步骤 0 Job_Outcome 作为每个作业步骤的列包含在内。这以一种易于存储的格式为我提供了历史记录。