SQL Server 代理作业和可用性组

noj*_*lag 43 sql-server sql-server-2012 sql-server-agent availability-groups

我正在寻找处理 SQL Server 2012 可用性组中计划的 SQL Server 代理作业的最佳实践。也许我错过了一些东西,但是在目前的状态下,我觉得 SQL Server 代理并没有真正与 SQL2012 的这个强大功能集成在一起。

如何让计划的 SQL 代理作业知道节点切换?例如,我有一个在主节点上运行的作业,它每小时加载数据。现在,如果主要服务器出现故障,我如何激活现在成为主要服务器的辅助服务器上的作业?

如果我总是在辅助上安排作业,它会失败,因为辅助是只读的。

Tho*_*ger 44

在您的 SQL Server 代理作业中,使用一些条件逻辑来测试当前实例是否正在为您在可用性组中寻找的特定角色提供服务:

if (select
        ars.role_desc
    from sys.dm_hadr_availability_replica_states ars
    inner join sys.availability_groups ag
    on ars.group_id = ag.group_id
    where ag.name = 'YourAvailabilityGroupName'
    and ars.is_local = 1) = 'PRIMARY'
begin
    -- this server is the primary replica, do something here
end
else
begin
    -- this server is not the primary replica, (optional) do something here
end
Run Code Online (Sandbox Code Playgroud)

所有这一切都是拉取本地副本的当前角色,如果它在PRIMARY角色中,那么如果它是主副本,您可以做任何您的工作需要做的事情。该ELSE块是可选的,但如果您的本地副本不是主要副本,它会处理可能的逻辑。

当然,'YourAvailabilityGroupName'将上述查询更改为您的实际可用性组名称。

不要将可用性组与故障转移群集实例混淆。实例是给定可用性组的主要副本还是次要副本不会影响服务器级对象,例如 SQL Server 代理作业等。


Tru*_*ubs 15

我没有在每个作业的基础上执行此操作(在决定继续之前检查每个作业的服务器状态),而是创建了一个在两台服务器上运行的作业来检查服务器处于什么状态。

  • 如果是主要的,则启用具有针对 AG 中数据库的步骤的任何作业。
  • 如果服务器是辅助服务器,则禁用任何以 AG 中的数据库为目标的作业。

这种方法提供了很多东西

  • 它适用于 AG 中没有数据库的服务器(或数据库中/出 AG 的混合)
  • 任何人都可以创建新作业而不必担心数据库是否在 AG 中(尽管他们必须记住将作业添加到另一台服务器)
  • 允许每个工作都有一个仍然有用的失败电子邮件(您所有的工作都有失败的电子邮件,对吗?)
  • 查看作业的历史记录时,您实际上可以看到该作业是否实际运行并执行了某些操作(这是主要的),而不是看到一长串实际上没有运行任何内容的成功列表(在次要的)

该脚本在下面的字段中检查数据库 如果此数据库在可用性组中,脚本将采取一些行动

这个过程在每台服务器上每 15 分钟执行一次。(附加评论以告知人们为什么该工作被禁用的额外好处)

/*
    This proc goes through all SQL Server agent jobs and finds any that refer to a database taking part in the availability Group 
    It will then enable/disable the job dependant on whether the server is the primary replica or not   
        Primary Replica = enable job
    It will also add a comment to the job indicating the job was updated by this proc
*/
CREATE PROCEDURE dbo.sp_HADRAgentJobFailover (@AGname varchar(200) = 'AG01' )
AS 

DECLARE @SQL NVARCHAR(MAX)

;WITH DBinAG AS (  -- This finds all databases in the AG and determines whether Jobs targeting these DB's should be turned on (which is the same for all db's in the AG)
SELECT  distinct
        runJobs = CASE WHEN role_desc = 'Primary' THEN 1 ELSE 0 END   --If this is the primary, then yes we want to run the jobs
        ,dbname = db.name
        ,JobDescription = CASE WHEN hars.role_desc = 'Primary'  -- Add the reason for the changing the state to the Jobs description
                THEN '~~~ [Enabled] using automated process (DBA_tools.dbo.sp_HADRAgentJobFailover) looking for jobs running against Primary Replica AG ~~~ '
                ELSE '~~~ [Diabled] using Automated process (DBA_tools.dbo.sp_HADRAgentJobFailover) because the job cant run on READ-ONLY Replica AG~~~ ' END 
FROM sys.dm_hadr_availability_replica_states hars
INNER JOIN sys.availability_groups ag ON ag.group_id = hars.group_id
INNER JOIN sys.Databases db ON  db.replica_id = hars.replica_id
WHERE is_local = 1
AND ag.Name = @AGname
) 

SELECT @SQL = (
SELECT DISTINCT N'exec msdb..sp_update_job @job_name = ''' + j.name + ''', @enabled = ' + CAST(d.runJobs AS VARCHAR) 
                + ',@description = ''' 
                + CASE WHEN j.description = 'No description available.' THEN JobDescription -- if there is no description just add our JobDescription
                       WHEN PATINDEX('%~~~%~~~',j.description) = 0 THEN j.description + '    ' + JobDescription  -- If our JobDescription is NOT there, add it
                       WHEN PATINDEX('%~~~%~~~',j.description) > 0 THEN SUBSTRING(j.description,1,CHARINDEX('~~~',j.description)-1) + d.JobDescription  --Replace our part of the job description with what we are doing.
                       ELSE d.JobDescription  -- Should never reach here...
                    END 
                + ''';'
FROM msdb.dbo.sysjobs j
INNER JOIN msdb.dbo.sysjobsteps s
INNER JOIN DBinAG d ON d.DbName =s.database_name     
ON j.job_id = s.job_id
WHERE j.enabled != d.runJobs   -- Ensure we only actually update the job, if it needs to change
FOR XML PATH ('')
)
PRINT REPLACE(@SQL,';',CHAR(10))
EXEC sys.sp_executesql @SQL
Run Code Online (Sandbox Code Playgroud)

它不是万无一失的,但对于夜间负载和每小时工作,它可以完成工作。

甚至比按计划运行此过程更好,而是运行它以响应警报 1480(AG 角色更改警报)。


tak*_*krl 9

我知道有两个概念可以实现这一点。

先决条件:根据 Thomas Stringer 的回答,我在两台服务器的主数据库中创建了两个函数:

CREATE FUNCTION [dbo].[svf_AgReplicaState](@availability_group_name sysname)
RETURNS bit
AS
BEGIN

if EXISTS(
    SELECT        ag.name
    FROM            sys.dm_hadr_availability_replica_states AS ars INNER JOIN
                             sys.availability_groups AS ag ON ars.group_id = ag.group_id
    WHERE        (ars.is_local = 1) AND (ars.role_desc = 'PRIMARY') AND (ag.name = @availability_group_name))

    RETURN 1

RETURN 0

END
GO

CREATE FUNCTION [dbo].[svf_DbReplicaState](@database_name sysname)
RETURNS bit
AS
BEGIN

IF EXISTS(
    SELECT        adc.database_name
    FROM            sys.dm_hadr_availability_replica_states AS ars INNER JOIN
                             sys.availability_databases_cluster AS adc ON ars.group_id = adc.group_id
    WHERE        (ars.is_local = 1) AND (ars.role_desc = 'PRIMARY') AND (adc.database_name = @database_name))

    RETURN 1
RETURN 0

END

GO
Run Code Online (Sandbox Code Playgroud)


  1. 如果作业不在主副本上执行,则终止作业

    对于这种情况,两台服务器上的每个作业都需要以下两个代码片段之一作为步骤 1:

    按组名检查:

    IF master.dbo.svf_AgReplicaState('my_group_name')=0
      raiserror ('This is not the primary replica.',2,1)
    
    Run Code Online (Sandbox Code Playgroud)

    按数据库名称检查:

    IF master.dbo.svf_AgReplicaState('my_db_name')=0
      raiserror ('This is not the primary replica.',2,1)
    
    Run Code Online (Sandbox Code Playgroud)

    如果您使用第二个,请注意系统数据库 - 根据定义,它们不能成为任何可用性组的一部分,因此它总是会失败。

    对于管理员用户来说,这两种方法都是开箱即用的。对于非管理员用户,您必须添加额外的权限,此处建议其中之一:

    GRANT VIEW SERVER STATE TO [user];
    GRANT VIEW ANY DEFINITION TO [user];
    
    Run Code Online (Sandbox Code Playgroud)

    如果您在第一步中将失败操作设置为退出作业报告成功,您将不会得到充满丑陋红十字标志的作业日志,因为主要作业它们会变成黄色警告标志。

    根据我们的经验,这并不理想。我们一开始采用了这种方法,但很快就无法找到实际有问题的作业,因为所有辅助副本作业都用警告消息使作业日志混乱。

    然后我们要做的是:

  2. 代理工作

    如果您采用此概念,您实际上需要为要执行的每个任务创建两个作业。第一个是“代理作业”,用于检查它是否正在主副本上执行。如果是这样,它会启动“工人作业”,如果不是,它只是优雅地结束,而不会用警告或错误消息使日志混乱。

    虽然我个人不喜欢在每台服务器上每个任务有两个作业的想法,但我认为它绝对更易于维护,并且您不必将步骤的失败操作设置为Quit job Reporting success,这有点尴尬的。

    对于作业,我们采用了命名方案。代理作业只是被调用{put jobname here}。工人作业称为{put jobname here} worker。这使得从代理自动启动工作任务成为可能。为此,我向两个主数据库添加了以下过程:

    CREATE procedure [dbo].[procStartWorkerJob](@jobId uniqueidentifier, @availabilityGroup sysname, @postfix sysname = ' worker') as
    declare @name sysname
    
    if dbo.svf_AgReplicaState(@availabilityGroup)=0
        print 'This is not the primary replica.'
    else begin
        SELECT @name = name FROM msdb.dbo.sysjobs where job_id = @jobId
    
        set @name = @name + @postfix
        if exists(select name from msdb.dbo.sysjobs where name = @name)
            exec msdb.dbo.sp_start_job @name
        else begin
            set @name = 'Job '''+@name+''' not found.'
            raiserror (@name ,2,1)
        end
    end
    GO
    
    Run Code Online (Sandbox Code Playgroud)

    这利用了svf_AgReplicaState上面显示的函数,您可以轻松地将其更改为使用数据库名称进行检查,而不是调用其他函数。

    在代理作业的唯一步骤中,您可以这样称呼它:

    exec procStartWorkerJob $(ESCAPE_NONE(JOBID)), '{my_group_name}'
    
    Run Code Online (Sandbox Code Playgroud)

    这利用此处此处所示的令牌来获取当前作业的 ID。然后该过程从 msdb 获取当前作业名称,附加 worker到它并使用sp_start_job.

    虽然这仍然不理想,但它使作业日志比以前的选项更整洁、更易于维护。此外,您始终可以让 sysadmin 用户运行代理作业,因此无需添加任何额外权限。