SQL 代理如何以及何时更新 next_run_date/next_run_time 值?

BBl*_*ake 16 sql-server-2008 sql-server sql-server-agent

我一直在研究 T-SQL 中的代码,以使用 msdb 数据库中的 sp_add_jobschedule proc 向 SQL 代理作业添加新计划。当我添加新计划(通常在特定日期/时间运行一次)并立即查看 sysjobschedules 和 sysschedules 中的值时,我可以看到新计划已添加并与我的 SQL 代理的 job_id 相关联工作。但是,next_run_date 和 next_run_time 的值在其中为 0。当我在 2 或 3 分钟后再次查看它们时,它们仍然显示为 0。但是,当我再过 5 或 10 分钟后再回来时,它现在可以正确显示与下一次计划运行相对应的日期和时间值。

所以我的问题是:

  • 这些值多久更新一次?
  • 更新这些值的过程是什么?
  • 如果我要添加一个计划,比如说,未来 1 分钟,这是否意味着该作业将不会运行,因为 next_run_date/time 尚未更新?

我用来添加新计划的代码示例:

exec msdb.dbo.sp_add_jobschedule @job_id = @jobID
                    , @name = @JobName
                    , @enabled = 1
                    , @freq_type = 1
                    , @freq_interval = 0
                    , @freq_subday_type = 0
                    , @freq_subday_interval = 0
                    , @freq_relative_interval = 0
                    , @freq_recurrence_factor = 0
                    , @active_start_date = @ScheduleRunDate
                    , @active_end_date = 99991231
                    , @active_start_time = @ScheduleRunTime
                    , @active_end_time = 235959
Run Code Online (Sandbox Code Playgroud)

其中@jobID 是binary(16),它保存有问题的作业的job_id,@ScheduleRunDate 和@ScheduleRunTime 是分别带有日期和时间的INT。

Aar*_*and 21

简答

看起来数据msdb.dbo.sysjobschedules是由 SQL 代理中的后台线程更新的,标识为SQLAgent - Schedule Saver,每 20 分钟(或频率更低,如果xp_sqlagent_notify没有被调用并且在此期间没有作业运行)。

为了更准确的信息,看看next_scheduled_run_datemsdb.dbo.sysjobactivity。每当更改作业或运行作业时,都会实时更新。作为额外的好处,它sysjobactivity以正确的方式存储数据(作为日期时间列),使其比那些愚蠢的 INT 更容易使用。

这是简短的回答:

sysjobschedules反映真相可能需要长达 20 分钟的时间;但是,sysjobactivity将始终是最新的。如果你想要更多关于这个的细节,或者我是怎么想出来的......


长答案

如果您想暂时跟随兔子,当您调用 时sp_add_jobschedule,这一系列事件就会启动:

msdb.dbo.sp_add_jobschedule == calls ==> msdb.dbo.sp_add_schedule
                                         msdb.dbo.sp_attach_schedule

msdb.dbo.sp_attach_schedule == calls ==> msdb.dbo.sp_sqlagent_notify

msdb.dbo.sp_sqlagent_notify == calls ==> msdb.dbo.xp_sqlagent_notify
Run Code Online (Sandbox Code Playgroud)

现在,我们不能再追兔子了,因为我们无法真正窥探到底是什么xp_sqlagent_notify。但我认为我们可以假设这个扩展过程与 Agent 服务交互并告诉它这个特定的工作和时间表发生了变化。通过运行服务器端跟踪,我们可以立即看到 SQL 代理调用了以下动态 SQL:

exec sp_executesql N'DECLARE @nextScheduledRunDate DATETIME 
  SET @nextScheduledRunDate = msdb.dbo.agent_datetime(@P1, @P2) 
  UPDATE msdb.dbo.sysjobactivity 
    SET next_scheduled_run_date = @nextScheduledRunDate 
    WHERE session_id = @P3 AND job_id = @P4',
N'@P1 int,@P2 int,@P3 int,@P4 uniqueidentifier',
20120819,181600,5,'36924B24-9706-4FD7-8B3A-1F9F0BECB52C'
Run Code Online (Sandbox Code Playgroud)

好像sysjobactivity是即时更新,而且sysjobschedules只是按计划更新。如果我们将新的时间表更改为每天一次,例如

@freq_type=4, 
@freq_interval=1, 
@freq_subday_type=1, 
@freq_subday_interval=0, 
@freq_relative_interval=0, 
@freq_recurrence_factor=1, 
Run Code Online (Sandbox Code Playgroud)

我们仍然看到sysjobactivity如上的立即更新,然后在作业完成后再次更新。各种更新来自 SQL 代理中的后台和其他线程,例如:

SQLAgent - Job Manager
SQLAgent - Update job activity
SQLAgent - Job invocation engine
SQLAgent - Schedule Saver
Run Code Online (Sandbox Code Playgroud)

后台线程(“Schedule Saver”线程)最终会出现并更新sysjobschedules;从我最初的调查来看,这似乎是每 20 分钟一次,并且仅xp_sqlagent_notify在由于自上次运行以来对作业所做的更改而被调用时才会发生(我没有进行任何进一步的测试以查看如果一项作业已如果“Schedule Saver”线程同时更新两者,则更改并运行另一个 - 我怀疑必须如此,但将其作为练习留给读者)。

我不确定 20 分钟的周期是否与 SQL 代理启动时、午夜或特定于机器的时间有偏移。在同一物理服务器上的两个不同实例上,“Schedule Saver”线程sysjobschedules在两个实例上几乎同时更新- 一个是 18:31:37 & 18:51:37,一个是 18:31:39 & 18:51:39 另一边。我没有在这些服务器上同时启动 SQL Server 代理,但是启动时间很可能有 20 分钟的偏移。我对此表示怀疑,但我现在没有时间通过​​在其中之一上重新启动 Agent 并等待更多更新发生来确认。

我知道是谁做的,什么时候发生的,因为我在那里放了一个触发器并捕获了它,以防我在跟踪中找不到它,或者我无意中将它过滤掉了。

CREATE TABLE dbo.JobAudit
(
  [action] CHAR(1),
  [table] CHAR(1), 
  hostname SYSNAME NOT NULL DEFAULT HOST_NAME(), 
  appname SYSNAME  NOT NULL DEFAULT PROGRAM_NAME(),
  dt DATETIME2     NOT NULL DEFAULT SYSDATETIME()
);

CREATE TRIGGER dbo.schedule1 ON dbo.sysjobactivity FOR INSERT
AS
  INSERT dbo.JobAudit([action], [table] SELECT 'I', 'A';
GO
CREATE TRIGGER dbo.schedule2 ON dbo.sysjobactivity FOR UPDATE
AS
  INSERT dbo.JobAudit([action], [table] SELECT 'U', 'A';
GO
CREATE TRIGGER dbo.schedule3 ON dbo.sysjobschedules FOR INSERT
AS
  INSERT dbo.JobAudit([action], [table] SELECT 'I', 'S';
GO
CREATE TRIGGER dbo.schedule4 ON dbo.sysjobschedules FOR UPDATE
AS
  INSERT dbo.JobAudit([action], [table] SELECT 'U', 'S';
GO
Run Code Online (Sandbox Code Playgroud)

也就是说,使用标准跟踪并不难,这个跟踪甚至是作为非动态 DML 实现的:

UPDATE msdb.dbo.sysjobschedules 
  SET next_run_date = 20120817, 
      next_run_time = 20000 
 WHERE (job_id = 0xB87B329BFBF7BA40B30D9B27E0B120DE 
 and schedule_id = 8)
Run Code Online (Sandbox Code Playgroud)

如果您想运行更过滤的跟踪来跟踪此行为随着时间的推移(例如,通过 SQL 代理重新启动而不是按需持续),您可以运行具有 appname = 'SQLAgent - Schedule Saver'...

所以我认为,如果你想立即知道下一次运行时间,看看sysjobactivity,而不是sysjobschedules。当活动发生或由 通知时,该表由代理或其后台线程(“更新作业活动”、“作业管理器”和“作业调用引擎”)直接更新xp_sqlagent_notify

但是请注意,很容易弄乱任一表 - 因为没有防止从这些表中删除数据的保护措施。(例如,如果您决定清理,您可以轻松地从活动表中删除该作业的所有行。)在这种情况下,我不确定 SQL Server 代理如何获取或保存下一次运行日期。也许值得在以后有空闲时间进行更多调查......