it-swarm.com.de

SQL Server-Agent-Jobs und Verfügbarkeitsgruppen

Ich suche nach bewährten Methoden für den Umgang mit geplanten SQL Server-Agent-Jobs in SQL Server 2012-Verfügbarkeitsgruppen. Vielleicht habe ich etwas verpasst, aber beim aktuellen Status habe ich das Gefühl, dass der SQL Server-Agent nicht wirklich in diese großartige SQL2012-Funktion integriert ist.

Wie kann ich einen geplanten SQL-Agentenjob auf einen Knotenwechsel aufmerksam machen? Zum Beispiel läuft auf dem Primärknoten ein Job, der stündlich Daten lädt. Wie kann ich den Job auf der Sekundärseite aktivieren, die jetzt zur Primärseite wird, wenn die Primärdatenbank ausfällt?

Wenn ich den Job immer auf der Sekundärseite plane, schlägt er fehl, da die Sekundärseite schreibgeschützt ist.

40
nojetlag

Lassen Sie in Ihrem SQL Server-Agent-Job eine bedingte Logik prüfen, ob die aktuelle Instanz die bestimmte Rolle erfüllt, nach der Sie in Ihrer Verfügbarkeitsgruppe suchen:

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

Dazu wird lediglich die aktuelle Rolle des lokalen Replikats abgerufen. Wenn es sich um die Rolle PRIMARY handelt, können Sie alles tun, was Ihr Job tun muss, wenn es sich um das primäre Replikat handelt. Der Block ELSE ist optional, dient jedoch der möglichen Logik, wenn Ihr lokales Replikat nicht primär ist.

Natürlich ändern Sie 'YourAvailabilityGroupName' in der obigen Abfrage zu Ihrem tatsächlichen Verfügbarkeitsgruppennamen.

Verwechseln Sie Verfügbarkeitsgruppen nicht mit Failoverclusterinstanzen. Ob die Instanz das primäre oder sekundäre Replikat für eine bestimmte Verfügbarkeitsgruppe ist, wirkt sich nicht auf Objekte auf Serverebene aus, z. B. SQL Server Agent-Jobs usw.

42
Thomas Stringer

Anstatt dies pro Job zu tun (indem ich jeden Job auf den Status des Servers überprüfe, bevor ich mich entscheide, fortzufahren), habe ich einen Job erstellt, der auf beiden Servern ausgeführt wird, um zu überprüfen, in welchem ​​Status sich der Server befindet.

  • Wenn es primär ist, aktivieren Sie jeden Job, dessen Schritt auf eine Datenbank in der AG abzielt.
  • Wenn der Server sekundär ist, deaktivieren Sie alle Jobs, die auf eine Datenbank in der AG abzielen.

Dieser Ansatz bietet eine Reihe von Dingen

  • es funktioniert auf Servern, auf denen keine Datenbanken in AG vorhanden sind (oder eine Mischung aus Dbs In/Out von AGs).
  • jeder kann einen neuen Job erstellen und muss sich keine Gedanken darüber machen, ob sich die Datenbank in einer AG befindet (obwohl er daran denken muss, den Job dem anderen Server hinzuzufügen).
  • Ermöglicht jedem Job eine Fehler-E-Mail, die nützlich bleibt (alle Ihre Jobs haben Fehler-E-Mails, oder?)
  • Wenn Sie den Verlauf eines Jobs anzeigen, können Sie tatsächlich sehen, ob der Job tatsächlich ausgeführt wurde und etwas getan hat (dies ist der primäre), anstatt eine lange Liste von Erfolgen zu sehen, die tatsächlich nichts ausgeführt haben (auf dem sekundären).

das Skript überprüft die Datenbank im Feld unten if this database is in an Availability Group the script will take some action

Dieser Prozess wird alle 15 Minuten auf jedem Server ausgeführt. (hat den zusätzlichen Bonus, einen Kommentar anzuhängen, um die Leute darüber zu informieren, warum der Job deaktiviert wurde)

/*
    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

Es ist nicht narrensicher, aber für Nachtladungen und stündliche Arbeiten erledigt es die Arbeit.

Noch besser, als diese Prozedur nach einem Zeitplan ausführen zu lassen, stattdessen als Reaktion auf Alert 1480 (AG-Rollenwechsel-Alert) auszuführen.

15
Trubs

Mir sind zwei Konzepte bekannt, um dies zu erreichen.

Voraussetzung: Basierend auf der Antwort von Thomas Stringer habe ich zwei Funktionen in der Master-Datenbank unserer beiden Server erstellt:

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. Beenden Sie einen Job, wenn er nicht auf dem primären Replikat ausgeführt wird

    In diesem Fall benötigt jeder Job auf beiden Servern eines der beiden folgenden Codefragmente als Schritt 1:

    Überprüfen Sie anhand des Gruppennamens:

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

    Überprüfen Sie anhand des Datenbanknamens:

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

    Wenn Sie diese zweite verwenden, achten Sie jedoch auf die Systemdatenbanken - per Definition können sie nicht Teil einer Verfügbarkeitsgruppe sein, sodass sie für diese immer fehlschlagen.

    Beide sind für Administratorbenutzer sofort einsatzbereit. Für Benutzer ohne Administratorrechte müssen Sie zusätzliche Berechtigungen hinzufügen, von denen eine vorgeschlagen wird hier :

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

    Wenn Sie die Fehleraktion in diesem ersten Schritt auf Jobberichterfolg beenden Setzen, wird das Jobprotokoll nicht mit hässlichen roten Kreuzzeichen gefüllt, da der Hauptjob für den Hauptjob gelb wird Warnschilder stattdessen.

    Nach unserer Erfahrung ist dies nicht ideal. Wir haben diesen Ansatz zunächst übernommen, aber schnell den Überblick über die Suche nach Jobs verloren, bei denen tatsächlich ein Problem aufgetreten ist, da alle sekundären Replikatjobs das Jobprotokoll mit Warnmeldungen überfüllten.

    Was wir dann wollten, ist:

  2. Proxy-Jobs

    Wenn Sie dieses Konzept übernehmen, müssen Sie tatsächlich zwei Jobs pro Aufgabe erstellen, die Sie ausführen möchten. Der erste ist der "Proxy-Job", der prüft, ob er auf dem primären Replikat ausgeführt wird. In diesem Fall wird der "Worker-Job" gestartet. Wenn nicht, wird er ordnungsgemäß beendet, ohne das Protokoll mit Warn- oder Fehlermeldungen zu überladen.

    Ich persönlich mag die Idee, zwei Jobs pro Aufgabe auf jedem Server zu haben, nicht, aber ich denke, sie ist definitiv wartbarer, und Sie müssen die Fehleraktion des Schritts nicht auf Erfolg beim Beenden der Jobberichterstattung setzen, was etwas umständlich ist.

    Für die Jobs haben wir ein Namensschema übernommen. Der Proxy-Job heißt einfach {put jobname here}. Der Arbeiterjob heißt {put jobname here} worker. Auf diese Weise kann das Starten des Worker-Jobs vom Proxy aus automatisiert werden. Zu diesem Zweck habe ich beiden Master-DBs das folgende Verfahren hinzugefügt:

    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 '''[email protected]+''' not found.'
            raiserror (@name ,2,1)
        end
    end
    GO
    

    Dies nutzt das svf_AgReplicaState Funktion oben gezeigt, können Sie diese leicht ändern, um sie anhand des Datenbanknamens zu überprüfen, indem Sie stattdessen die andere Funktion aufrufen.

    Innerhalb des einzigen Schritts des Proxy-Jobs rufen Sie ihn folgendermaßen auf:

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

    Dies verwendet Tokens wie gezeigt hier und hier , um die ID des aktuellen Jobs zu erhalten. Die Prozedur erhält dann den aktuellen Jobnamen von msdb und hängt  worker dazu und startet den Worker-Job mit sp_start_job.

    Dies ist zwar immer noch nicht ideal, sorgt jedoch dafür, dass die Auftragsprotokolle übersichtlicher und wartbarer sind als bei der vorherigen Option. Sie können den Proxy-Job auch immer mit einem Sysadmin-Benutzer ausführen lassen, sodass das Hinzufügen zusätzlicher Berechtigungen nicht erforderlich ist.

9
takrl

Wenn es sich bei dem Datenladevorgang um eine einfache Abfrage oder einen Prozeduraufruf handelt, können Sie den Job auf beiden Knoten erstellen und anhand der Aktualisierbarkeitseigenschaft der Datenbank bestimmen lassen, ob er der primäre Knoten ist, bevor der Datenladevorgang ausgeführt wird:

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

-- Data Load code goes under here

END
3
Yasin

Es ist immer besser, einen neuen Jobschritt zu erstellen, der prüft, ob es sich um ein primäres Replikat handelt. Dann ist alles in Ordnung, um mit der Jobausführung fortzufahren. Wenn es sich um ein sekundäres Replikat handelt, beenden Sie den Job. Scheitern Sie nicht, sonst werden weiterhin unnötige Benachrichtigungen gesendet. Stoppen Sie stattdessen den Job, damit er abgebrochen wird und keine Benachrichtigungen gesendet werden, wenn diese Jobs auf dem sekundären Replikat ausgeführt werden.

Unten finden Sie das Skript zum Hinzufügen eines ersten Schritts für einen bestimmten Job.

Hinweis zum Ausführen des Skripts:

  • Ersetzen Sie 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' durch Job_ID
  • Ersetzen Sie 'YYYYYYYYYYYYYYYYYYYYYYYYY' durch Job_Name
  • Wenn mehrere Verfügbarkeitsgruppen vorhanden sind, legen Sie den AG-Namen in der Variablen @AGNameToCheck_IfMoreThanSingleAG fest, welche AG auf ihren Replikatstatus überprüft werden soll.

  • Beachten Sie auch, dass dieses Skript auch auf Servern ohne Verfügbarkeitsgruppen gut funktionieren sollte. Wird nur für SQL Server-Versionen 2012 und höher ausgeführt.

            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
    
1
Masood Hashim

Eine andere Möglichkeit besteht darin, in jeden Job, der zuerst ausgeführt werden soll, einen Schritt mit dem folgenden Code einzufügen:

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

Legen Sie diesen Schritt fest, um mit dem nächsten Schritt zum Erfolg fortzufahren und den Erfolg der Jobberichterstattung bei einem Fehler zu beenden.

Ich finde es sauberer, einen zusätzlichen Schritt hinzuzufügen, anstatt einem vorhandenen Schritt zusätzliche Logik hinzuzufügen.

0
KoeKk

Eine andere, neuere Option ist die Verwendung von master.sys.fn_hadr_is_primary_replica ('DbName'). Ich fand dies sehr hilfreich, wenn ich SQL Agent für die Datenbankwartung verwende (zusammen mit einem Cursor, den ich seit Jahren verwende) und wenn ich eine ETL oder eine andere datenbankspezifische Aufgabe ausführe. Der Vorteil ist, dass die Datenbank herausgegriffen wird, anstatt die gesamte Verfügbarkeitsgruppe zu betrachten ... wenn Sie dies benötigen. Es macht es auch viel unwahrscheinlicher, dass ein Befehl für eine Datenbank ausgeführt wird, die auf der Primärdatenbank "war". Angenommen, während der Jobausführung ist ein automatisches Failover aufgetreten, das sich jetzt auf einer sekundären Replik befindet. Die oben genannten Methoden, die sich das primäre Replikat ansehen, werfen einen Blick darauf und werden nicht aktualisiert. Beachten Sie, dass dies nur ein anderer Weg ist, um sehr ähnliche Ergebnisse zu erzielen und bei Bedarf eine genauere Kontrolle zu erzielen. Der Grund, warum diese Methode nicht diskutiert wurde, als diese Frage gestellt wurde, ist, dass Microsoft diese Funktion erst nach der Veröffentlichung von SQL 2014 freigegeben hat. Im Folgenden finden Sie einige Beispiele für die Verwendung dieser Funktion:

   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

Wenn Sie dies für die Wartung der Benutzerdatenbank verwenden möchten, verwende ich Folgendes:

/*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

Ich hoffe das ist ein hilfreicher Tipp!

0
SQL_Hacker

Ich benutze das:

if (select primary_replica from sys.dm_hadr_availability_group_states) = @@SERVERNAME begin
... paste your t-sql here ...

end
0
Aleksey Vitsko