SQL Server Agent Zadania i grupy dostępności

37

Szukam najlepszych praktyk w radzeniu sobie z zaplanowanymi zadaniami agenta programu SQL Server w grupach dostępności programu SQL Server 2012. Może coś przeoczyłem, jednak w obecnym stanie wydaje mi się, że SQL Server Agent nie jest tak naprawdę zintegrowany z tą wspaniałą funkcją SQL2012.

Jak mogę poinformować zaplanowane zadanie agenta SQL o przełączeniu węzła? Na przykład mam zadanie uruchomione w węźle podstawowym, który ładuje dane co godzinę. Teraz, gdy podstawowa ulegnie awarii, jak mogę aktywować zadanie na dodatkowej, która teraz staje się podstawowa?

Jeśli zaplanuję zadanie zawsze jako pomocnicze, nie powiedzie się, ponieważ wtedy pomocnicze jest tylko do odczytu.

nojetlag
źródło

Odpowiedzi:

40

W ramach zadania agenta programu SQL Server sprawdź logikę warunkową, aby sprawdzić, czy bieżąca instancja pełni określoną rolę, której szukasz w grupie dostępności:

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

Wszystko to polega na wyciągnięciu bieżącej roli lokalnej repliki, a jeśli jest ona w PRIMARYroli, możesz zrobić wszystko, co musisz zrobić, jeśli jest to replika podstawowa. ELSEBlok jest opcjonalny, ale jest możliwy do obsługi logiki, jeśli lokalna replika nie jest podstawowym.

Oczywiście zmień 'YourAvailabilityGroupName'powyższe zapytanie na rzeczywistą nazwę grupy dostępności.

Nie należy mylić grup dostępności z instancjami klastra pracy awaryjnej. To, czy instancja jest repliką podstawową czy dodatkową dla danej grupy dostępności, nie wpływa na obiekty na poziomie serwera, takie jak zadania agenta SQL Server itd.

Thomas Stringer
źródło
14

Zamiast robić to dla poszczególnych zadań (sprawdzanie każdego zadania pod kątem stanu serwera przed podjęciem decyzji o kontynuacji), stworzyłem zadanie działające na obu serwerach, aby sprawdzić, w jakim stanie jest serwer.

  • Jeśli jest to podstawowa, włącz dowolne zadanie, które ma krok ukierunkowany na bazę danych w AG.
  • Jeśli serwer jest pomocniczy, wyłącz wszelkie zadania ukierunkowane na bazę danych w AG.

Takie podejście zapewnia wiele rzeczy

  • działa na serwerach, na których nie ma baz danych w AG (lub kombinacji Db in / out of AGs)
  • każdy może utworzyć nowe zadanie i nie musi się martwić, czy baza danych znajduje się w AG (chociaż musi pamiętać, aby dodać zadanie do innego serwera)
  • Pozwala każdemu zadaniu na wiadomość e-mail o błędzie, która pozostaje przydatna (wszystkie zadania mają e-maile o błędzie, prawda?)
  • Przeglądając historię zadania, faktycznie widzisz, czy zadanie faktycznie zostało uruchomione i coś zrobiło (to jest najważniejsze), zamiast zobaczyć długą listę sukcesów, które w rzeczywistości niczego nie uruchomiły (na dodatkowej stronie)

skrypt sprawdza bazę danych w polu poniżej jeśli ta baza danych należy do grupy dostępności, skrypt podejmie pewne działania

Ten proces jest wykonywany co 15 minut na każdym serwerze. (ma dodatkową zaletę dodawania komentarza informującego ludzi, dlaczego praca została wyłączona)

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

To nie jest głupi dowód, ale w przypadku nocnych obciążeń i godzinowych zadań wykonuje zadanie.

Nawet lepiej niż uruchomienie tej procedury zgodnie z harmonogramem, zamiast tego uruchom ją w odpowiedzi na Alert 1480 (alert zmiany roli AG).

Truby
źródło
9

Mam świadomość dwóch koncepcji, aby to osiągnąć.

Warunek: na podstawie odpowiedzi Thomasa Stringera utworzyłem dwie funkcje w głównej bazie danych naszych dwóch serwerów:

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. Zadanie należy zakończyć, jeśli nie zostanie wykonane w podstawowej replice

    W takim przypadku każde zadanie na obu serwerach wymaga jednego z następujących dwóch fragmentów kodu jako kroku 1:

    Sprawdź według nazwy grupy:

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

    Sprawdź według nazwy bazy danych:

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

    Jeśli użyjesz tego drugiego, uważaj jednak na systemowe bazy danych - z definicji nie mogą one należeć do żadnej grupy dostępności, więc dla nich zawsze zawiedzie.

    Oba działają od razu po wyjęciu z pudełka dla użytkowników administracyjnych. W przypadku użytkowników niebędących administratorami musisz dodać dodatkowe uprawnienia, jedno z nich sugerowane tutaj :

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

    Jeśli ustawisz akcję niepowodzenia jako Zakończ raportowanie zadania w pierwszym kroku, nie dostaniesz dziennika pracy pełnego brzydkich znaków czerwonego krzyża, dla głównego zadania zamiast tego zamieniają się w żółte znaki ostrzegawcze.

    Z naszego doświadczenia wynika, że ​​nie jest to idealne rozwiązanie. Najpierw przyjęliśmy to podejście, ale szybko straciliśmy orientację w poszukiwaniu zadań, które faktycznie miały problem, ponieważ wszystkie zadania wtórnej repliki zaśmiecały dziennik zadań komunikatami ostrzegawczymi.

    Następnie wybraliśmy:

  2. Zadania proxy

    Jeśli zastosujesz tę koncepcję, będziesz musiał utworzyć dwa zadania dla każdego zadania, które chcesz wykonać. Pierwszym z nich jest „zadanie proxy”, które sprawdza, czy jest wykonywane w podstawowej replice. Jeśli tak, uruchamia „zadanie robocze”, jeśli nie, kończy się z gracją bez zaśmiecania dziennika komunikatami ostrzegawczymi lub komunikatami o błędach.

    Chociaż osobiście nie podoba mi się pomysł posiadania dwóch zadań na zadanie na każdym serwerze, myślę, że jest to zdecydowanie łatwiejsze w utrzymaniu i nie musisz ustawiać działania niepowodzenia w kroku Wyjście z raportowania zadań , co jest nieco niezręczny.

    W przypadku zadań przyjęliśmy schemat nazewnictwa. Zadanie proxy jest właśnie wywoływane {put jobname here}. Nazywa się zadanie pracownika {put jobname here} worker. Umożliwia to zautomatyzowanie uruchamiania zadania roboczego z serwera proxy. Aby to zrobić, dodałem następującą procedurę do obu master dbs:

    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
    

    Wykorzystuje to svf_AgReplicaStatefunkcję pokazaną powyżej, można ją łatwo zmienić, sprawdzając przy użyciu nazwy bazy danych, wywołując inną funkcję.

    Z poziomu jedynego kroku zadania proxy wywołujesz go w następujący sposób:

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

    Wykorzystuje tokeny, jak pokazano tutaj i tutaj, aby uzyskać identyfikator bieżącego zadania. Procedura następnie pobiera bieżącą nazwę zadania z msdb, dołącza  workerdo niej i uruchamia zadanie robocze za pomocą sp_start_job.

    Chociaż nadal nie jest to idealne, sprawia, że ​​dzienniki zadań są bardziej uporządkowane i łatwiejsze w utrzymaniu niż poprzednia opcja. Ponadto zawsze możesz uruchomić zadanie proxy z użytkownikiem sysadmin, więc dodanie dodatkowych uprawnień nie jest konieczne.

takrl
źródło
3

Jeśli proces ładowania danych jest prostym zapytaniem lub wywołaniem procedury, możesz utworzyć zadanie w obu węzłach i pozwolić mu określić, czy jest to główny węzeł na podstawie właściwości Aktualizowalności bazy danych, zanim wykona on proces ładowania danych:

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

-- Data Load code goes under here

END
Yasin
źródło
1

Zawsze lepiej jest utworzyć nowy krok zadania, który sprawdza, czy jest to replika podstawowa, wtedy wszystko jest w porządku, aby kontynuować wykonywanie zadania, w przeciwnym razie, jeśli jest to replika pomocnicza, zatrzymaj zadanie. Nie zawiedź zadania, w przeciwnym razie będzie wysyłało niepotrzebne powiadomienia. Zamiast tego zatrzymaj zadanie, aby zostało anulowane i żadne powiadomienia nie są wysyłane za każdym razem, gdy zadania te są wykonywane w replice pomocniczej.

Poniżej znajduje się skrypt, aby dodać pierwszy krok do określonego zadania.

Uwaga, aby wykonać skrypt:

  • Zamień „XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX” na Job_ID
  • Zamień „YYYYYYYYYYYYYYYYYYYYYYYYYYY” na Job_Name
  • Jeśli istnieje wiele grup dostępności, ustaw nazwę AG w zmiennej @AGNameToCheck_IfMoreThanSingleAG, aby ustalić, która AG powinna być sprawdzona pod kątem stanu repliki.

  • Pamiętaj też, że ten skrypt powinien działać dobrze nawet na serwerach, które nie mają grup dostępności. Będzie działać tylko dla SQL Server wersji 2012 i późniejszych.

            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
    
Masood Hashim
źródło
0

Innym sposobem jest wstawienie kroku do każdego zadania, które powinno zostać uruchomione jako pierwsze, z następującym kodem:

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

Ustaw ten krok, aby przejść do następnego kroku o sukcesie i zakończyć raportowanie powodzenia zadania po awarii.

Uważam, że łatwiej jest dodać dodatkowy krok zamiast dodawać logikę do istniejącego kroku.

KoeKk
źródło
0

Inną, nowszą opcją jest użycie master.sys.fn_hadr_is_primary_replica („DbName”). Znalazłem to bardzo pomocne, gdy korzystam z agenta SQL do obsługi bazy danych (w połączeniu z kursorem, którego używałem od lat), a także podczas wykonywania ETL lub innego zadania specyficznego dla bazy danych. Zaletą jest to, że wyróżnia bazę danych zamiast patrzeć na całą grupę dostępności ... jeśli tego potrzebujesz. Znacznie bardziej prawdopodobne jest, że polecenie zostanie wykonane względem bazy danych, która „była” na podstawowym, ale powiedzmy, że podczas wykonywania zadania nastąpiło automatyczne przełączenie awaryjne, a teraz znajduje się ono w replice wtórnej. Powyższe metody, które sprawdzają replikę podstawową, sprawdzają się i nie aktualizują. Pamiętaj, że jest to po prostu inny sposób na uzyskanie bardzo podobnych wyników i zapewnienie bardziej szczegółowej kontroli, jeśli jej potrzebujesz. Ponadto powodem, dla którego ta metoda nie była omawiana podczas zadawania tego pytania, jest to, że Microsoft nie wydał tej funkcji, dopóki nie wydano SQL 2014. Poniżej znajduje się kilka przykładów, w jaki sposób można użyć tej funkcji:

   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

Jeśli chcesz użyć tego do konserwacji bazy danych użytkowników, używam tego:

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

Mam nadzieję, że to przydatna wskazówka!

SQL_Hacker
źródło
0

Używam tego:

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

end
aleksey vitsko
źródło