SQL Agent Job: Determine how long it has been running

后端 未结 3 2132
萌比男神i
萌比男神i 2021-02-14 14:14

The Scenario

There are certain SQL Agent Jobs that are scheduled to run every few minutes throughout the day.

There are legitimate times when it

3条回答
  •  囚心锁ツ
    2021-02-14 15:15

    What you're doing manually sounds like what's known as a "watch dog loop". Basically, an SQL job that starts and/or monitors agent jobs, killing them if need be.

    The code below was taken from here, and should help, removing the need for you to manually monitor and kill jobs if they've been running for a lengthy period of time:

    /****************************************************************
    --This SQL will take a list of SQL Agent jobs (names must match),
    --start them so they're all running together, and then
    --monitor them, not quitting until all jobs have completed.
    --
    --In essence, it's an SQL "watchdog" loop to start and monitor SQL Agent Jobs
    --
    --Code from http://cc.davelozinski.com/code/sql-watchdog-loop-start-monitor-sql-agent-jobs
    --
    ****************************************************************/
    SET NOCOUNT ON 
    
    -------- BEGIN ITEMS THAT NEED TO BE CONFIGURED --------
    
    --The amount of time to wait before checking again 
    --to see if the jobs are still running.
    --Should be in hh:mm:ss format. 
    DECLARE @WaitDelay VARCHAR(8) = '00:00:20'
    
    --Job timeout. Eg, if the jobs are running longer than this, kill them.
    DECLARE @TimeoutMinutes INT = 240
    
    DECLARE @JobsToRunTable TABLE
    (
        JobName NVARCHAR(128) NOT NULL,
        JobID UNIQUEIDENTIFIER NULL,
        Running INT NULL
    )
    
    --Insert the names of the SQL jobs here. Last two values should always be NULL at this point.
    --Names need to match exactly, so best to copy/paste from the SQL Server Agent job name.
    INSERT INTO @JobsToRunTable (JobName, JobID, Running) VALUES ('NameOfFirstSQLAgentJobToRun',NULL,NULL)
    INSERT INTO @JobsToRunTable (JobName, JobID, Running) VALUES ('NameOfSecondSQLAgentJobToRun',NULL,NULL)
    INSERT INTO @JobsToRunTable (JobName, JobID, Running) VALUES ('NameOfXSQLAgentJobToRun',NULL,NULL)
    
    -------- NOTHING FROM HERE DOWN SHOULD NEED TO BE CONFIGURED --------
    
    DECLARE @ExecutionStatusTable TABLE
    (
        JobID UNIQUEIDENTIFIER PRIMARY KEY, -- Job ID which will be a guid
        LastRunDate INT, LastRunTime INT, -- Last run date and time
        NextRunDate INT, NextRunTime INT, -- Next run date and time
        NextRunScheduleID INT, -- an internal schedule id
        RequestedToRun INT, RequestSource INT, RequestSourceID VARCHAR(128),
        Running INT,    -- 0 or 1, 1 means the job is executing
        CurrentStep INT, -- which step is running
        CurrentRetryAttempt INT, -- retry attempt
        JobState INT -- 0 = Not idle or suspended, 1 = Executing, 2 = Waiting For Thread,
                         -- 3 = Between Retries, 4 = Idle, 5 = Suspended, 
                         -- 6 = WaitingForStepToFinish, 7 = PerformingCompletionActions
    )
    
    DECLARE @JobNameToRun NVARCHAR(128) = NULL
    DECLARE @IsJobRunning BIT = 1
    DECLARE @AreJobsRunning BIT = 1
    DECLARE @job_owner sysname = SUSER_SNAME()
    DECLARE @JobID UNIQUEIDENTIFIER = null
    DECLARE @StartDateTime DATETIME = GETDATE()
    DECLARE @CurrentDateTime DATETIME = null
    DECLARE @ExecutionStatus INT = 0
    DECLARE @MaxTimeExceeded BIT = 0
    
    --Loop through and start every job
    DECLARE dbCursor CURSOR FOR SELECT JobName FROM @JobsToRunTable
    OPEN dbCursor FETCH NEXT FROM dbCursor INTO @JobNameToRun
    WHILE @@FETCH_STATUS = 0
    BEGIN
        EXEC [msdb].[dbo].sp_start_job @JobNameToRun
        FETCH NEXT FROM dbCursor INTO @JobNameToRun
    END
    CLOSE dbCursor
    DEALLOCATE dbCursor
    
    print '*****************************************************************'
    print 'Jobs started. ' + CAST(@StartDateTime as varchar)
    print '*****************************************************************'
    
    --Debug (if needed)
    --SELECT * FROM @JobsToRunTable
    
    WHILE 1=1 AND @AreJobsRunning = 1
    BEGIN
    
        --This has to be first with the delay to make sure the jobs
        --have time to actually start up and are recognized as 'running'
        WAITFOR DELAY @WaitDelay 
    
        --Reset for each loop iteration
        SET @AreJobsRunning = 0
    
        --Get the currently executing jobs by our user name
        INSERT INTO @ExecutionStatusTable
        EXECUTE [master].[dbo].xp_sqlagent_enum_jobs 1, @job_owner
    
        --Debug (if needed)
        --SELECT 'ExecutionStatusTable', * FROM @ExecutionStatusTable
    
        --select every job to see if it's running
        DECLARE dbCursor CURSOR FOR 
            SELECT x.[Running], x.[JobID], sj.name 
            FROM @ExecutionStatusTable x 
            INNER JOIN [msdb].[dbo].sysjobs sj ON sj.job_id = x.JobID
            INNER JOIN @JobsToRunTable jtr on sj.name = jtr.JobName
        OPEN dbCursor FETCH NEXT FROM dbCursor INTO @IsJobRunning, @JobID, @JobNameToRun
    
        --Debug (if needed)
        --SELECT x.[Running], x.[JobID], sj.name 
        --  FROM @ExecutionStatusTable x 
        --  INNER JOIN msdb.dbo.sysjobs sj ON sj.job_id = x.JobID
        --  INNER JOIN @JobsToRunTable jtr on sj.name = jtr.JobName
    
        WHILE @@FETCH_STATUS = 0
        BEGIN
            --bitwise operation to see if the loop should continue
            SET @AreJobsRunning = @AreJobsRunning | @IsJobRunning
    
            UPDATE @JobsToRunTable
            SET Running = @IsJobRunning, JobID = @JobID
            WHERE JobName = @JobNameToRun
    
            --Debug (if needed)
            --SELECT 'JobsToRun', * FROM @JobsToRunTable
    
            SET @CurrentDateTime=GETDATE()
    
            IF @IsJobRunning = 1
            BEGIN -- Job is running or finishing (not idle)
    
                IF DATEDIFF(mi, @StartDateTime, @CurrentDateTime) > @TimeoutMinutes
                BEGIN     
                    print '*****************************************************************'
                    print @JobNameToRun + ' exceeded timeout limit of ' + @TimeoutMinutes + ' minutes. Stopping.'
                    --Stop the job
                    EXEC [msdb].[dbo].sp_stop_job @job_name = @JobNameToRun
                END
                ELSE
                BEGIN
                    print @JobNameToRun + ' running for ' + CONVERT(VARCHAR(25),DATEDIFF(mi, @StartDateTime, @CurrentDateTime)) + ' minute(s).'
                END
            END
    
            IF @IsJobRunning = 0 
            BEGIN
                --Job isn't running
                print '*****************************************************************'
                print @JobNameToRun + ' completed or did not run. ' + CAST(@CurrentDateTime as VARCHAR)
            END
    
            FETCH NEXT FROM dbCursor INTO @IsJobRunning, @JobID, @JobNameToRun
    
        END -- WHILE @@FETCH_STATUS = 0
        CLOSE dbCursor
        DEALLOCATE dbCursor
    
        --Clear out the table for the next loop iteration
        DELETE FROM @ExecutionStatusTable
    
        print '*****************************************************************'
    
    END -- WHILE 1=1 AND @AreJobsRunning = 1
    
    SET @CurrentDateTime = GETDATE()
    print 'Finished at ' + CAST(@CurrentDateTime as varchar)
    print CONVERT(VARCHAR(25),DATEDIFF(mi, @StartDateTime, @CurrentDateTime)) + ' minutes total run time.'
    

提交回复
热议问题