Задания агента SQL Server и группы доступности

37

Я ищу рекомендации по работе с запланированными заданиями агента SQL Server в группах доступности SQL Server 2012. Может быть, я что-то упустил, однако в текущем состоянии я чувствую, что агент SQL Server на самом деле не интегрирован с этой замечательной функцией SQL2012.

Как я могу сделать запланированное задание агента SQL осведомленным о переключении узла? Например, у меня есть задание, работающее на основном узле, который загружает данные каждый час. Теперь, если основной отказывает, как я могу активировать работу на дополнительном, который теперь становится основным?

Если я планирую задание всегда на дополнительном устройстве, оно завершается сбоем, потому что вспомогательное только для чтения.

nojetlag
источник

Ответы:

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 (или комбинация входов и выходов Db из 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

Это не защищает от дурака, но для ночных нагрузок и почасовой работы он выполняет свою работу.

Даже лучше, чем запускать эту процедуру по расписанию, вместо этого запустите ее в ответ на предупреждение 1480 (предупреждение об изменении роли AG).

Trubs
источник
9

Я знаю две концепции для достижения этой цели.

Предварительное условие: основываясь на ответе Томаса Стрингера, я создал две функции в главной базе данных наших двух серверов:

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:

    Проверка по имени группы:

    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)

    Если вы используете этот второй, будьте осторожны с системными базами данных, хотя - по определению они не могут быть частью какой-либо группы доступности, поэтому для них это всегда будет неудачей.

    Оба из них работают из коробки для пользователей-администраторов. Для пользователей без прав администратора вы должны добавить дополнительные разрешения, одно из которых предлагается здесь :

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

    Если на этом первом шаге для действия при сбое установить « Завершение отчета об успешном выполнении задания», журнал заданий не будет заполнен страшными знаками красного креста, а вместо основного задания они превратятся в желтые предупреждающие знаки.

    По нашему опыту, это не идеально. Сначала мы применили этот подход, но быстро потеряли контроль над поиском заданий, в которых действительно возникла проблема, поскольку все задания вторичной реплики загромождали журнал заданий предупреждающими сообщениями.

    То, что мы тогда пошли на это:

  2. Работа прокси

    Если вы примете эту концепцию, вам фактически потребуется создать две работы для каждой задачи, которую вы хотите выполнить. Первый - это «прокси-задание», которое проверяет, выполняется ли оно на первичной реплике. Если это так, он запускает «рабочее задание», если нет, он просто грациозно завершается, не загромождая журнал предупреждениями или сообщениями об ошибках.

    Хотя мне лично не нравится идея иметь по два задания на задачу на каждом сервере, я думаю, что она определенно более удобна в обслуживании, и вам не нужно настраивать действие сбоя на шаге « Завершить успешное создание отчетов» , что немного неловко.

    Для рабочих мест мы приняли схему именования. Работа прокси просто называется {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}'

    Здесь используются токены, как показано здесь и здесь, чтобы получить идентификатор текущей работы. Затем процедура получает текущее имя задания из msdb, добавляет  workerк нему и запускает рабочее задание, используя sp_start_job.

    Хотя это все еще не идеально, он сохраняет журналы заданий более аккуратными и удобными для обслуживания, чем предыдущий вариант. Кроме того, вы всегда можете запустить задание прокси с пользователем sysadmin, поэтому добавление каких-либо дополнительных разрешений не требуется.

takrl
источник
3

Если процесс загрузки данных представляет собой простой запрос или вызов процедуры, то вы можете создать задание на обоих узлах и позволить ему определить, является ли его основным узлом на основе свойства Updateability базы данных, прежде чем он выполнит процесс загрузки данных:

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

-- Data Load code goes under here

END
Ясин
источник
1

Всегда лучше создать новый шаг задания, который проверяет, является ли он первичной репликой, тогда все в порядке, чтобы продолжить выполнение задания, иначе, если это вторичная реплика, остановите задание. Не подведите задание, иначе оно будет отправлять ненужные уведомления. Вместо этого остановите задание, чтобы оно было отменено и уведомления не отправлялись всякий раз, когда эти задания выполняются на вторичной реплике.

Ниже приведен скрипт для добавления первого шага для конкретной работы.

Примечание для выполнения скрипта:

  • Заменить «XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX» на Job_ID
  • Заменить «ГГГГГГГГГГГГГГГГГГ» на Job_Name
  • Если имеется несколько групп доступности, задайте имя AG в переменной @AGNameToCheck_IfMoreThanSingleAG, чтобы указать, какой 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
Масуд Хашим
источник
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

Задайте этот шаг, чтобы перейти к следующему шагу в случае успеха и выйти из отчета об успешном выполнении задания при сбое.

Я считаю более понятным добавить дополнительный шаг вместо добавления дополнительной логики к существующему шагу.

KoeKk
источник
0

Другой, более новый вариант, использует master.sys.fn_hadr_is_primary_replica ('DbName'). Я обнаружил, что это очень полезно при использовании агента SQL для обслуживания базы данных (в сочетании с курсором, которым я пользовался годами), а также при выполнении 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

Я надеюсь, что это полезный совет!

SQL_Hacker
источник
0

Я использую это:

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

end
алексей вицко
источник