SQL Server 2012可用性グループでスケジュールされたSQL Serverエージェントジョブを処理するベストプラクティスを探しています。見落としがあったかもしれませんが、現状では、SQL Serverエージェントはこの優れたSQL2012機能と実際には統合されていないと感じています。
スケジュールされたSQLエージェントジョブにノード切り替えを認識させるにはどうすればよいですか?たとえば、プライマリノードで1時間ごとにデータをロードするジョブを実行しています。ここで、プライマリがダウンした場合、セカンダリになっているジョブをどのようにアクティブにできますか?
常にセカンダリでジョブをスケジュールすると、セカンダリは読み取り専用であるため失敗します。
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エージェントジョブなどのサーバーレベルのオブジェクトには影響しません。
ジョブごとにこれを行うのではなく(続行を決定する前にすべてのジョブでサーバーの状態を確認します)、両方のサーバーで実行されるジョブを作成して、サーバーの状態を確認しました。
このアプローチは多くのものを提供します
このプロシージャは、各サーバーで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
ばかげているわけではありませんが、夜間のロードと毎時のジョブの場合は、ジョブが完了します。
この手順をスケジュールに従って実行するよりも、代わりにアラート1480(AGロール変更アラート)に応答して実行します。
これを実現するための2つの概念を知っています。
前提条件:Thomas Stringerの回答に基づいて、2つのサーバーのマスターデータベースに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として次の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];
この最初のステップで失敗アクションをQuit job reports successに設定した場合、メインジョブの醜い赤い十字記号でいっぱいのジョブログは取得されません。代わりに黄色の警告標識に変わります。
私たちの経験から、これは理想的ではありません。最初はこのアプローチを採用しましたが、すべてのセカンダリレプリカジョブが警告メッセージでジョブログを雑然としていたため、実際に問題があったジョブを見つけることについてすぐに足を踏み外しました。
次に行ったのは:
プロキシジョブ
この概念を採用する場合、実際には、実行するタスクごとに2つのジョブを作成する必要があります。 1つ目は、プライマリレプリカで実行されているかどうかを確認する「プロキシジョブ」です。そうである場合は、「ワーカージョブ」を開始し、そうでない場合は、警告メッセージやエラーメッセージでログが乱雑になることなく、正常に終了します。
私は個人的には、すべてのサーバーでタスクごとに2つのジョブを実行するという考えは好きではありませんが、より保守しやすく、ステップの失敗アクションをQuit jobに設定する必要がないと思います成功を報告する、これは少し厄介です。
仕事には、命名方式を採用しました。プロキシジョブは{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}'
これは here および here のようにトークンを使用して、現在のジョブのIDを取得します。次に、このプロシージャはmsdbから現在のジョブ名を取得し、 worker
を追加して、sp_start_job
を使用してワーカージョブを開始します。
これはまだ理想的ではありませんが、以前のオプションよりもジョブログを整理し、保守しやすくします。また、常にsysadminユーザーでプロキシジョブを実行できるため、追加の権限を追加する必要はありません。
データロードプロセスが単純なクエリまたはプロシージャコールの場合、両方のノードでジョブを作成し、データロードプロセスを実行する前に、データベースのUpdateabilityプロパティに基づいてプライマリノードかどうかを判断させることができます。
IF (SELECT CONVERT(sysname,DatabasePropertyEx(DB_NAME(),'Updateability'))) != 'READ_ONLY'
BEGIN
-- Data Load code goes under here
END
プライマリレプリカであるかどうかを確認する新しいジョブステップを作成してから、ジョブの実行を続行しても問題はありません。セカンダリレプリカである場合は、ジョブを停止します。ジョブを失敗させないでください。そうしないと、不要な通知が送信され続けます。代わりに、ジョブを停止してキャンセルし、セカンダリレプリカでこれらのジョブが実行されるたびに通知が送信されないようにします。
以下は、特定のジョブの最初のステップを追加するスクリプトです。
スクリプトを実行することに注意してください。
複数の可用性グループがある場合は、変数@AGNameToCheck_IfMoreThanSingleAGにAG名を設定して、レプリカの状態を確認するAGを指定します。
また、このスクリプトは、可用性グループを持たないサーバーでも正常に機能することに注意してください。 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
別の方法は、次のコードを使用して、最初に実行する必要がある各ジョブにステップを挿入することです。
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
このステップを設定して、成功時に次のステップに進み、失敗した場合に成功を報告するジョブを終了します。
既存のステップにロジックを追加するのではなく、ステップを追加する方がきれいだと思います。
別のより新しいオプションは、master.sys.fn_hadr_is_primary_replica( 'DbName')を使用することです。 SQLエージェントを使用してデータベースのメンテナンス(私が長年使用してきたカーソルと組み合わせて)を実行する場合、およびETLまたはその他のデータベース固有のタスクを実行する場合にも、これは非常に役立ちました。利点は、可用性グループ全体ではなく、データベースが選択されることです...それが必要な場合。また、プライマリで「使用された」データベースに対してコマンドが実行される可能性ははるかに低くなりますが、ジョブの実行中に自動フェイルオーバーが発生し、現在はセカンダリレプリカで実行されているとします。プライマリレプリカを確認する上記のメソッドは、1つの確認で更新されません。必要に応じて、これは非常に類似した結果を達成し、より詳細な制御を行うための別の方法にすぎないことに注意してください。また、この質問が行われたときにこの方法が説明されなかったのは、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
これが役立つヒントになれば幸いです。
私はこれを使います:
if (select primary_replica from sys.dm_hadr_availability_group_states) = @@SERVERNAME begin
... paste your t-sql here ...
end