web-dev-qa-db-fra.com

Travaux et groupes de disponibilité de l'Agent SQL Server

Je recherche les meilleures pratiques pour gérer les travaux planifiés de l'Agent SQL Server dans les groupes de disponibilité SQL Server 2012. Peut-être que j'ai raté quelque chose, mais dans l'état actuel, je pense que l'Agent SQL Server n'est pas vraiment intégré à cette excellente fonctionnalité SQL2012.

Comment puis-je informer un travail d'agent SQL planifié d'un changement de nœud? Par exemple, j'ai un travail en cours d'exécution sur le nœud principal qui charge les données chaque heure. Maintenant, si le primaire tombe en panne, comment puis-je activer le travail sur le secondaire qui devient maintenant primaire?

Si je planifie le travail toujours sur le secondaire, il échoue car le secondaire est en lecture seule.

40
nojetlag

Dans votre travail SQL Server Agent, disposez d'une logique conditionnelle pour vérifier si l'instance actuelle sert le rôle particulier que vous recherchez sur votre groupe de disponibilité:

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

Tout cela ne fait que tirer le rôle actuel de la réplique locale, et si c'est dans le rôle PRIMARY, vous pouvez faire tout ce que votre travail doit faire s'il s'agit de la réplique principale. Le bloc ELSE est facultatif, mais il gère la logique possible si votre réplique locale n'est pas principale.

Bien sûr, changez 'YourAvailabilityGroupName' dans la requête ci-dessus pour le nom de votre groupe de disponibilité réel.

Ne confondez pas les groupes de disponibilité avec les instances de cluster de basculement. Que l'instance soit le réplica principal ou secondaire pour un groupe de disponibilité donné n'affecte pas les objets au niveau du serveur, comme les travaux de l'Agent SQL Server, etc.

42
Thomas Stringer

Plutôt que de le faire sur une base par tâche (en vérifiant l'état de chaque serveur avant de décider de continuer), j'ai créé une tâche en cours d'exécution sur les deux serveurs pour vérifier pour voir dans quel état se trouve le serveur.

  • S'il s'agit de son principal, activez tout travail comportant une étape ciblant une base de données dans l'AG.
  • Si le serveur est secondaire, désactivez tout travail ciblant une base de données dans l'AG.

Cette approche fournit un certain nombre de choses

  • il fonctionne sur des serveurs où il n'y a pas de bases de données dans AG (ou un mélange d'entrées/sorties de Db dans les AG)
  • tout le monde peut créer un nouveau travail et ne pas avoir à se soucier de savoir si la base de données est dans un AG (bien qu'ils ne doivent pas oublier d'ajouter le travail à l'autre serveur)
  • Permet à chaque travail d'avoir un e-mail d'échec qui reste utile (tous vos travaux ont des e-mails d'échec non?)
  • Lorsque vous consultez l'historique d'un travail, vous pouvez réellement voir si le travail s'est réellement exécuté et a fait quelque chose (ce qui est le principal), plutôt que de voir une longue liste de succès qui n'a en fait rien exécuté (sur le secondaire)

le script vérifie la base de données dans le champ ci-dessous if this database is in an Availability Group the script will take some action

Ce proc est exécuté toutes les 15 minutes sur chaque serveur. (a l'avantage supplémentaire d'ajouter un commentaire pour informer les gens pourquoi le travail a été désactivé)

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

Ce n'est pas infaillible, mais pour les charges de nuit et les travaux horaires, il fait le travail.

Mieux encore que d'exécuter cette procédure selon un calendrier, exécutez-la à la place en réponse à l'alerte 1480 (alerte de changement de rôle AG).

15
Trubs

Je connais deux concepts pour y parvenir.

Prérequis: Sur la base de la réponse de Thomas Stringer, j'ai créé deux fonctions dans la base de données master de nos deux serveurs:

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. Faire terminer un travail s'il n'est pas exécuté sur la réplique principale

    Dans ce cas, chaque travail sur les deux serveurs nécessite l'un des deux extraits de code suivants à l'étape 1:

    Vérifier par nom de groupe:

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

    Vérifier par nom de base de données:

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

    Si vous utilisez cette seconde, méfiez-vous des bases de données système - par définition, elles ne peuvent pas faire partie d'un groupe de disponibilité, donc elles échoueront toujours pour celles-ci.

    Ces deux fonctions sont prêtes à l'emploi pour les utilisateurs administrateurs. Pour les utilisateurs non administrateurs, vous devez ajouter des autorisations supplémentaires, l'une d'entre elles étant suggérée ici :

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

    Si vous définissez l'action d'échec sur Quitter la notification des travaux réussie lors de cette première étape, vous n'aurez pas le journal des travaux rempli de signes de croix rouges laids, pour le travail principal, ils deviendront jaunes signes d'avertissement à la place.

    D'après notre expérience, ce n'est pas idéal. Nous avons d'abord adopté cette approche, mais nous avons rapidement perdu la trace de la recherche de travaux qui avaient réellement un problème, car tous les travaux de réplique secondaire encombraient le journal des travaux avec des messages d'avertissement.

    Ce que nous avons ensuite recherché, c'est:

  2. Travaux proxy

    Si vous adoptez ce concept, vous devrez en fait créer deux travaux par tâche que vous souhaitez effectuer. Le premier est le "travail proxy" qui vérifie s'il est exécuté sur la réplique principale. Si tel est le cas, il démarre le "job de travail", sinon, il se termine simplement sans encombrer le journal avec des messages d'avertissement ou d'erreur.

    Bien que personnellement, je n'aime pas l'idée d'avoir deux tâches par tâche sur chaque serveur, je pense que c'est définitivement plus facile à gérer, et vous n'avez pas à définir l'action d'échec de l'étape sur Quittez la réussite de la notification des travaux, ce qui est un peu gênant.

    Pour les emplois, nous avons adopté un schéma de dénomination. Le travail proxy est simplement appelé {put jobname here}. Le travail du travailleur s'appelle {put jobname here} worker. Cela permet d'automatiser le démarrage du travail de travail à partir du proxy. Pour ce faire, j'ai ajouté la procédure suivante aux deux dbs maîtres:

    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
    

    Cela utilise le svf_AgReplicaState fonction ci-dessus, vous pouvez facilement changer cela pour vérifier en utilisant le nom de la base de données à la place en appelant l'autre fonction.

    Depuis la seule étape du travail proxy, vous l'appelez comme ceci:

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

    Cela utilise des jetons comme indiqué ici et ici pour obtenir l'identifiant du travail en cours. La procédure obtient ensuite le nom du travail actuel à partir de msdb, ajoute  worker et démarre le travail du travailleur à l'aide de sp_start_job.

    Bien que ce ne soit toujours pas idéal, il conserve les journaux des travaux plus ordonnés et maintenables que l'option précédente. En outre, vous pouvez toujours exécuter le travail proxy avec un utilisateur sysadmin, il n'est donc pas nécessaire d'ajouter des autorisations supplémentaires.

9
takrl

Si le processus de chargement des données est une simple requête ou un appel de procédure, vous pouvez créer le travail sur les deux nœuds et le laisser déterminer s'il s'agit du nœud principal basé sur la propriété Updateability de la base de données, avant d'exécuter le processus de chargement des données:

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

-- Data Load code goes under here

END
3
Yasin

Il est toujours préférable de créer une nouvelle étape de travail qui vérifie s'il s'agit d'un réplica principal, puis tout va bien pour continuer l'exécution du travail sinon s'il s'agit d'un réplica secondaire, puis arrêtez le travail. N'échouez pas le travail sinon il continuera à envoyer des notifications inutiles. Au lieu de cela, arrêtez le travail afin qu'il soit annulé et qu'aucune notification ne soit envoyée chaque fois que ces travaux sont exécutés sur le réplica secondaire.

Vous trouverez ci-dessous le script pour ajouter une première étape pour un travail spécifique.

Remarque pour exécuter le script:

  • Remplacez "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" par Job_ID
  • Remplacez 'YYYYYYYYYYYYYYYYYYYYYYYYYY' par Job_Name
  • S'il existe plusieurs groupes de disponibilité, définissez le nom AG dans la variable @AGNameToCheck_IfMoreThanSingleAG quant à l'AG à vérifier pour son état de réplique.

  • Notez également que ce script devrait bien fonctionner même sur les serveurs qui n'ont pas de groupes de disponibilité. S'exécutera uniquement pour les versions de SQL Server 2012 et au-delà.

            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

Une autre façon consiste à insérer une étape dans chaque travail, qui doit s'exécuter en premier, avec le code suivant:

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

Définissez cette étape pour passer à l'étape suivante en cas de succès et pour quitter le travail qui rapporte le succès en cas d'échec.

Je trouve plus propre d'ajouter une étape supplémentaire au lieu d'ajouter une logique supplémentaire à une étape existante.

0
KoeKk

Une autre option, plus récente, utilise master.sys.fn_hadr_is_primary_replica ('DbName'). J'ai trouvé cela très utile lors de l'utilisation de l'Agent SQL pour effectuer la maintenance de la base de données (couplé avec un curseur que j'ai utilisé pendant des années) et également lors de l'exécution d'un ETL ou d'une autre tâche spécifique à la base de données. L'avantage est qu'il distingue la base de données au lieu de regarder l'ensemble du groupe de disponibilité ... si c'est ce dont vous avez besoin. Il est également beaucoup plus improbable qu'une commande soit exécutée sur une base de données qui "se trouvait" sur le serveur principal, mais disons qu'un basculement automatique s'est produit pendant l'exécution du travail, et qu'il se trouve désormais sur une réplique secondaire. Les méthodes ci-dessus qui regardent le réplica principal jettent un coup d'œil et ne sont pas mises à jour. Gardez à l'esprit que c'est juste une manière différente d'obtenir des résultats très similaires et de donner un contrôle plus granulaire, si vous en avez besoin. En outre, la raison pour laquelle cette méthode n'a pas été discutée lorsque cette question a été posée est que Microsoft n'a publié cette fonction qu'après la publication de SQL 2014. Voici quelques exemples d'utilisation de cette fonction:

   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

Si vous souhaitez l'utiliser pour la maintenance de la base de données utilisateur, voici ce que j'utilise:

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

J'espère que c'est un conseil utile!

0
SQL_Hacker

J'utilise ceci:

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

end
0
Aleksey Vitsko