SQL Serverエージェントのジョブと可用性グループ


37

SQL Server 2012可用性グループでスケジュールされたSQL Serverエージェントジョブを処理するベストプラクティスを探しています。たぶん何かを見逃したかもしれませんが、現在の状態では、SQL Server Agentはこの優れたSQL2012機能と実際には統合されていないと感じています。

スケジュールされたSQLエージェントジョブにノードの切り替えを認識させるにはどうすればよいですか?たとえば、1時間ごとにデータをロードするジョブをプライマリノードで実行しています。プライマリがダウンした場合、プライマリになったセカンダリでジョブをアクティブにするにはどうすればよいですか?

セカンダリで常にジョブをスケジュールすると、セカンダリは読み取り専用であるため失敗します。


回答:


40

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

これは、ローカルレプリカの現在のロールをプルするPRIMARYだけです。ロール内にある場合、それがプライマリレプリカである場合、ジョブが必要とすることは何でもできます。ELSEブロックはオプションですが、あなたのローカルレプリカがプライマリでない場合、それは可能なロジックを処理します。

もちろん、'YourAvailabilityGroupName'上記のクエリを実際の可用性グループ名に変更してください。

可用性グループとフェールオーバークラスターインスタンスを混同しないでください。インスタンスが特定の可用性グループのプライマリまたはセカンダリレプリカであるかどうかは、SQL Serverエージェントジョブなどのサーバーレベルのオブジェクトには影響しません。


14

ジョブごとにこれを行うのではなく(続行する前にすべてのジョブでサーバーの状態を確認する)、両方のサーバーで実行するジョブを作成して、サーバーの状態を確認しました。

  • プライマリの場合、AG内のデータベースを対象とするステップを持つジョブを有効にします。
  • サーバーがセカンダリの場合、AGのデータベースを対象とするジョブを無効にします。

このアプローチは多くのことを提供します

  • AGにデータベースがない(またはAGのイン/アウトのDbが混在する)サーバーで動作します
  • 誰でも新しいジョブを作成でき、dbが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

絶対確実ではありませんが、夜間の負荷と1時間ごとのジョブの場合は、ジョブが完了します。

この手順をスケジュールに従って実行するよりも、アラート1480(AGロール変更アラート)に応答して実行するよりもさらに優れています。


9

これを実現するための2つの概念を知っています。

前提条件:Thomas Stringerの答えに基づいて、2つのサーバーのmaster dbに2つの関数を作成しました。

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


  1. プライマリレプリカで実行されていないジョブを終了する

    この場合、両方のサーバー上のすべてのジョブには、ステップ1として次の2つのコードスニペットのいずれかが必要です。

    グループ名で確認:

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

    データベース名で確認:

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

    この2番目のデータベースを使用する場合は、システムデータベースに注意してください-定義上、これらのデータベースは可用性グループの一部にはなれません。したがって、それらは常に失敗します。

    これらは両方とも、管理者ユーザー向けにそのまま使用できます。管理者以外のユーザーの場合、追加のアクセス許可を追加する必要があります。そのうちの1つがここで提案されています

    GRANT VIEW SERVER STATE TO [user];
    GRANT VIEW ANY DEFINITION TO [user];

    この最初の手順で失敗アクションを「ジョブレポートの成功終了する」に設定すると、ジョブログがredい赤い十字印でいっぱいになることはありません。メインジョブの代わりに黄色の警告記号に変わります。

    私たちの経験から、これは理想的ではありません。最初はこのアプローチを採用しましたが、すべてのセカンダリレプリカジョブが警告メッセージでジョブログを乱雑にしているため、実際に問題が発生したジョブを見つけることに関してすぐに道を失いました。

    それから私たちが行ったのは:

  2. プロキシジョブ

    この概念を採用する場合、実際に実行するタスクごとに2つのジョブを作成する必要があります。1つ目は、プライマリレプリカで実行されているかどうかを確認する「プロキシジョブ」です。そうである場合、「ワーカージョブ」を開始し、そうでない場合、警告またはエラーメッセージでログを乱雑にすることなく、正常に終了します。

    私は個人的に、すべてのサーバー上でタスクごとに2つのジョブを持っていることのアイデアのようにありませんが、私はそれがdefinetlyより保守だと思う、とあなたはするステップの障害アクションを設定する必要はありません成功を報告して仕事を辞めビットです、ぎこちない。

    ジョブについては、命名スキームを採用しました。プロキシジョブは単に呼び出され{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

    これは上記のsvf_AgReplicaState関数を使用します。他の関数を呼び出すことで、代わりにデータベース名を使用してチェックするように簡単に変更できます。

    プロキシジョブの唯一のステップ内から、次のように呼び出します。

    exec procStartWorkerJob $(ESCAPE_NONE(JOBID)), '{my_group_name}'

    これは、ここここに示すトークンを使用して、現在のジョブのIDを取得します。次に、プロシージャはmsdbから現在のジョブ名を取得し、 workerそれに追加して、を使用してワーカージョブを開始しsp_start_jobます。

    これはまだ理想的ではありませんが、ジョブログを以前のオプションよりも整頓された状態に維持します。また、sysadminユーザーでプロキシジョブをいつでも実行できるため、追加のアクセス許可を追加する必要はありません。


3

データロードプロセスが単純なクエリまたはプロシージャコールの場合、両方のノードでジョブを作成し、データロードプロセスを実行する前に、データベースのUpdateabilityプロパティに基づいてプライマリノードかどうかを判断できます。

IF (SELECT CONVERT(sysname,DatabasePropertyEx(DB_NAME(),'Updateability'))) != 'READ_ONLY'
BEGIN

-- Data Load code goes under here

END

1

プライマリレプリカであるかどうかを確認する新しいジョブステップを作成することをお勧めします。セカンダリレプリカである場合はジョブの実行を続行し、その後ジョブを停止します。ジョブを失敗させないでください。失敗すると、不要な通知が送信され続けます。代わりに、ジョブをキャンセルして、セカンダリレプリカでこれらのジョブが実行されるたびに通知が送信されないようにします。

以下は、特定のジョブの最初のステップを追加するスクリプトです。

スクリプトを実行するための注意:

  • 「XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX」をJob_IDに置き換えます
  • 'YYYYYYYYYYYYYYYYYYYYYYYYYY'をJob_Nameに置き換えます
  • 複数の可用性グループがある場合は、AGのレプリカ状態を確認する必要があるAG名を変数@AGNameToCheck_IfMoreThanSingleAGに設定します。

  • また、このスクリプトは、可用性グループを持たないサーバーでも正常に動作することに注意してください。SQL Serverバージョン2012以降でのみ実行されます。

            USE [msdb]
            GO
            EXEC msdb.dbo.sp_add_jobstep @job_id=N'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX', @step_name=N'CheckForSecondaryReplica', 
                    @step_id=1, 
                    @cmdexec_success_code=0, 
                    @on_success_action=3, 
                    @on_fail_action=2, 
                    @retry_attempts=0, 
                    @retry_interval=0, 
                    @os_run_priority=0, @subsystem=N'TSQL', 
                    @command=N'
            DECLARE @AGNameToCheck_IfMoreThanSingleAG VARCHAR(100)
            SET @AGNameToCheck_IfMoreThanSingleAG = ''AGName_IfMoreThanOneAG'' -- If there are Multiple AGs, then a single server can have Primary of one AG and Secondary of other. So Job creator has to define as to which AG needs to verified before the job is automatically run on Primary.
    
            DECLARE @NumberofAGs INT
            SELECT @NumberofAGs = COUNT(group_id) FROM sys.availability_groups ags
    
    
            IF(@NumberofAGs < 2)
                IF EXISTS(Select * FROM sys.dm_hadr_availability_replica_states hars WHERE role_desc = ''Secondary'' AND hars.is_local = 1)                 
                                    EXEC msdb.dbo.sp_stop_job N''YYYYYYYYYYYYYYYYYYYYYYYYYY'' ;
                                    --RAISERROR(''This is a Secondary Replica'',16,1)
    
            IF(@NumberofAGs >= 2)
                IF EXISTS(SELECT 1 FROM sys.availability_groups WHERE name = @AGNameToCheck_IfMoreThanSingleAG)
                BEGIN
                            IF EXISTS(Select * from  sys.availability_groups ag
                                            JOIN sys.dm_hadr_availability_replica_states hars
                                                        ON ag.group_id = hars.group_id
                                                        Where role_desc = ''Secondary''
                                                        AND hars.is_local = 1
                                                        AND ag.name = @AGNameToCheck_IfMoreThanSingleAG)
                            BEGIN
                                    EXEC msdb.dbo.sp_stop_job N''YYYYYYYYYYYYYYYYYYYYYYYYYY'' ;
                                    --RAISERROR(''This is a Secondary Replica'',16,1)
                            END
                END
                ELSE
                            BEGIN
                                    RAISERROR(''The Defined AG in the Variable is not a part of this Server. Please Check!!!!!!!!!!!'',16,1)
                            END', 
                    @database_name=N'master', 
                    @flags=0
            GO

0

別の方法は、次のコードを使用して、最初に実行する必要がある各ジョブにステップを挿入することです。

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
    AND ars.is_local = 1) <> 'PRIMARY'
BEGIN
   --We're on the secondary node, throw an error
   THROW 50001, 'Unable to execute job on secondary node',1
END

このステップを設定して、成功時に次のステップを続行し、失敗時に成功を報告するジョブを終了します。

既存のステップに追加のロジックを追加する代わりに、追加のステップを追加する方がわかりやすいと思います。


0

別の新しいオプションは、master.sys.fn_hadr_is_primary_replica( 'DbName')を使用しています。SQL Agentを使用してデータベースメンテナンスを行う場合(長年使用していたカーソルと組み合わせて)、ETLまたは他のデータベース固有のタスクを実行する場合、これは非常に便利です。利点は、可用性グループ全体を調べる代わりにデータベースを選択することです...それが必要な場合です。また、プライマリで「あった」データベースに対してコマンドが実行される可能性がはるかに低くなりますが、ジョブの実行中に自動フェールオーバーが発生し、セカンダリレプリカで実行されたとしましょう。プライマリレプリカを調べる上記のメソッドは、一見して更新しません。これは、非常によく似た結果を達成し、必要に応じてよりきめ細かな制御を行うための単なる別の方法であることに留意してください。また、この質問が尋ねられたときにこの方法が議論されなかった理由は、MicrosoftがSQL 2014がリリースされるまでこの関数をリリースしなかったためです。以下は、この関数の使用方法のサンプルです。

   IF master.dbo.fn_hadr_database_is_primary_replica('Admin') = 1
    BEGIN 
        -- do whatever you were going to do in the Primary:
        PRINT 'Doing stuff in the Primary Replica';
    END
ELSE 
    BEGIN 
        -- we're not in the Primary - exit gracefully:
        PRINT 'This is not the primary replica - exiting with success';
    END

ユーザーデータベースのメンテナンスにこれを使用する場合、これは私が使用するものです:

/*Below evaluates all user databases in the instance and gives stubs to do work; must change to get anything other than print statements*/
declare @dbname varchar(1000)
declare @sql nvarchar(4000)

declare AllUserDatabases cursor for
    select [name] from master.sys.databases
    where database_id > 4 --this excludes all sysdbs; if all but tempdb is desired, change to <> 2
    and [state] = 0

open AllUserDatabases
fetch AllUserDatabases into @dbname

while (@@FETCH_STATUS = 0)
    begin
    --PRINT @dbname
        set @sql = '
            IF master.sys.fn_hadr_is_primary_replica(''' + @dbname + ''') = 1
                BEGIN 
                    -- do whatever you are going to do in the Primary:
                    PRINT ''Doing stuff in the Primary Replica''
                END
            ELSE 
                BEGIN 
                    -- not in the Primary - exit gracefully:
                    PRINT ''This is not the primary replica - exiting with success''
                END             
        '
        exec sp_executesql @sql
        fetch AllUserDatabases into @dbname
    end
close AllUserDatabases
deallocate AllUserDatabases

これが参考になることを願っています!


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.