Comment créer une notification d'événement qui exécute un travail / une procédure lors de la mise en miroir des changements d'état


11

Je pose cette question dans la séquence de celle-ci Puis-je envoyer une chaîne via TCP en utilisant T-SQL?

Remus Rusanu expose ce qui semble être une solution optimale à mon problème, mais ... Je suis trop immature pour comprendre et faire tout ce qu'il dit.

Jusqu'à présent, je pense que ce dont j'ai besoin pour créer un événement de notification pour DATABASE_MIRRORING_STATE_CHANGE, ai-je raison?

comment puis-je créer cette notification d'événement lorsque son déclenchement insère une ligne dans une table, qui stocke un horodatage et un ID provenant de la notification.

jusqu'à présent, je configure une alerte par ID, chacun exécutant un travail comme celui-ci (cet exemple est pour ID = 1):

    DECLARE @state AS varchar(50);
    SELECT @state = mirroring_state_desc FROM SYS.database_mirroring WHERE mirroring_guid IS NOT NULL;
    IF (@state IS null) SET @state = ' ';
    INSERT INTO MirroringAlerts (DateTime, alertID, alertDesc, Sync, alertCreator) values (SYSDATETIME(), 1, 'Principal synchronized with W ', @state, @@SERVERNAME)

Fondamentalement, je crée un journal interne dans cette base de données:

CREATE TABLE [dbo].[MirroringAlerts](
    [DateTime] [datetime] NOT NULL,
    [alertID] [smallint] NOT NULL,
    [alertDesc] [nchar](50) NOT NULL,
    [Sync] [nchar](12) NOT NULL,
    [alertCreator] [nchar](128) NULL
) ON [PRIMARY]

Mais de cette façon ... les alertes ne sont pas déclenchées assez rapidement ... donc je perds des informations ...

Pouvez-vous me dire comment programmer ce comportement avec la création d'une notification d'événement pour l' événement Database Mirroring State Changed ?

Meilleures salutations

Réponses:


13

Étape 1: Créez un service pour recevoir les notifications et une file d'attente pour celui-ci:

use msdb;
go

create queue dbm_notifications_queue;
create service dbm_notification_service
    on queue dbm_notifications_queue
    ([http://schemas.microsoft.com/SQL/Notifications/PostEventNotification]);
go

create event notification dbm_notifications
    on server   
    for database_mirroring_state_change
    to service N'dbm_notification_service', N'current database';
go

Notez que j'utilise msdb, ce n'est pas un accident. Étant donné que les notifications d'événements au niveau du serveur sont envoyées depuis, msdbil est préférable que vous créiez également le point de terminaison de conversation opposé (la cible) msdb, ce qui implique que le service de destination et la file d'attente doivent également être déployés dans msdb.

Étape 2: créez la procédure de traitement des notifications d'événements:

use msdb;
go

create table dbm_notifications_errors (
    incident_time datetime not null,
    session_id int not null,
    has_rolled_back bit not null,
    [error_number] int not null,
    [error_message] nvarchar(4000) not null,
    [message_body] varbinary(max));
create clustered index cdx_dbm_notifications_errors 
    on dbm_notifications_errors  (incident_time);
go

create table mirroring_alerts (
    alert_time datetime not null,
    start_time datetime not null,
    processing_time datetime not null,
    database_id smallint not null,
    database_name sysname not null,
    [state] tinyint not null,
    [text_data] nvarchar(max),
    event_data xml not null);
create clustered index cdx_mirroring_alerts
    on mirroring_alerts (alert_time);   
go      

create procedure dbm_notifications_procedure
as
begin
    declare @dh uniqueidentifier, @mt sysname, @raw_body varbinary(max), @xml_body xml; 

    begin transaction;
    begin try;
        receive top(1)
            @dh = conversation_handle,
            @mt = message_type_name,
            @raw_body = message_body
        from dbm_notifications_queue;
        if N'http://schemas.microsoft.com/SQL/Notifications/EventNotification' = @mt
        begin
            set @xml_body = cast(@raw_body as xml);
             -- shred the XML and process it accordingly
             -- IMPORTANT! IMPORTANT!
             -- DO NOT LOOK AT sys.database_mirroring
             -- The view represents the **CURRENT** state
             -- This message reffers to an **EVENT** that had occured
             -- the current state may or may no be relevant for this **PAST** event
            declare @alert_time datetime
                , @start_time datetime
                , @processing_time datetime = getutcdate()
                , @database_id smallint 
                , @database_name sysname
                , @state tinyint
                , @text_data nvarchar(max);

            set @alert_time = @xml_body.value (N'(//EVENT_INSTANCE/PostTime)[1]', 'DATETIME');
            set @start_time = @xml_body.value (N'(//EVENT_INSTANCE/StartTime)[1]', 'DATETIME');
            set @database_id = @xml_body.value (N'(//EVENT_INSTANCE/DatabaseID)[1]', 'SMALLINT');
            set @database_name = @xml_body.value (N'(//EVENT_INSTANCE/DatabaseName)[1]', 'SYSNAME');
            set @state = @xml_body.value (N'(//EVENT_INSTANCE/State)[1]', 'TINYINT');
            set @text_data = @xml_body.value (N'(//EVENT_INSTANCE/TextData)[1]', 'NVARCHAR(MAX)');

            insert into mirroring_alerts (
                alert_time, 
                start_time,
                processing_time,
                database_id,
                database_name,
                [state],
                text_data,
                event_data)
            values (
                @alert_time, 
                @start_time,
                @processing_time,
                @database_id,
                @database_name,
                @state,
                @text_data,
                @xml_body);
        end
        else if N'http://schemas.microsoft.com/SQL/ServiceBroker/Error' = @mt
        begin
        set @xml_body = cast(@raw_body as xml);
        DECLARE @error INT
                , @description NVARCHAR(4000);
        WITH XMLNAMESPACES ('http://schemas.microsoft.com/SQL/ServiceBroker/Error' AS ssb)
        SELECT @error = CAST(@xml_body AS XML).value('(//ssb:Error/ssb:Code)[1]', 'INT'),
            @description = CAST(@xml_body AS XML).value('(//ssb:Error/ssb:Description)[1]', 'NVARCHAR(4000)');          

        insert into dbm_notifications_errors(
            incident_time,
            session_id, 
            has_rolled_back,
            [error_number],
            [error_message],
            [message_body])
        values (
            getutcdate(),
            @@spid,
            0,
            @error,
            @description,
            @raw_body);
            end conversation @dh;
        end
        else if N'http://schemas.microsoft.com/SQL/ServiceBroker/EndDialog' = @mt
        begin
            end conversation @dh;
        end
        commit;
    end try
    begin catch
        declare @xact_state int = xact_state(), 
            @error_number int = error_number(), 
            @error_message nvarchar(4000) = error_message(),
            @has_rolled_back bit = 0;
        if @xact_state = -1
        begin
            -- Doomed transaction, it must rollback
            rollback;
            set @has_rolled_back = 1;
        end
        else if @xact_state = 0
        begin
            -- transaction was already rolled back (deadlock?)
            set @has_rolled_back = 1;
        end
        insert into dbm_notifications_errors(
            incident_time,
            session_id, 
            has_rolled_back,
            [error_number],
            [error_message],
            [message_body])
        values (
            getutcdate(),
            @@spid,
            @has_rolled_back,
            @error_number,
            @error_message,
            @raw_body);
        if (@has_rolled_back = 0)
        begin
            commit;
        end
    end catch
end
go

L'écriture d'une procédure de courtier de services n'est pas votre code ordinaire. Il faut suivre certaines normes et il est très facile de s'éloigner en territoire sablonneux. Ce code montre quelques bonnes pratiques:

  • encapsuler la file d'attente de messages et le traitement dans une transaction. Aucune évidence, évident.
  • vérifiez toujours le type de message reçu. Une bonne procédure de courtier de services doit gérer Erroret EndDialogenvoyer les messages de manière appropriée en fermant la boîte de dialogue de son côté. Ne pas le faire entraîne des fuites de poignée (se sys.conversation_endpointsdéveloppe)
  • vérifiez toujours si un message a été retiré de la file d'attente par RECEIVE. Certains échantillons vérifient @@ rowcount après RECEIVE, ce qui est parfaitement OK. Cet exemple de code repose sur la vérification du nom du message (aucun message n'implique un nom de type de message NULL) et gère ce cas implicitement.
  • créer une table des erreurs de traitement. la nature d'arrière-plan des procédures activées par SSB, il est vraiment difficile de dépanner les erreurs si les messages disparaissent simplement sans trace.

En outre, ce code fait également du code de bonnes pratiques en ce qui concerne la tâche à accomplir (surveillance DBM):

  • faire la distinction entre post_time( quand la notification a-t-elle été envoyée? ), start_time( quand l'action qui a déclenché la notification a-t-elle commencé? ) et processing_time( quand la notification a-t-elle été traitée? ). post_timeet start_timesera probablement identique ou très proche, mais processing_timepeut être en secondes, heures, jours en dehors post_time. le plus intéressant pour l'audit est généralement post_time.
  • étant donné que le post_timeet le processing_timesont différents, il devrait être évident qu'une tâche de surveillance DBM dans une procédure activée par notification régulière n'a aucun intérêt à sys.database_mirroringregarder . Cette vue montrera l' état actuel au moment du traitement, qui peut être lié ou non à l'événement. Si le traitement se produit longtemps après la publication de l'événement (pensez à un arrêt de maintenance), le problème est évident, mais il peut également être traité dans un traitement «sain» si le DBM change d'état très rapidement et publie deux (ou plus) événements dans un ligne (ce qui se produit fréquemment): dans cette situation, le traitement, comme dans le code que vous avez publié, audite l'événement au fur et à mesure qu'il se produit, mais enregistre l'état actuel, final . La lecture d'un tel audit pourrait être très confuse plus tard.
  • auditez toujours l'événement XML d'origine. De cette façon, vous pouvez ultérieurement interroger ce XML pour toute information qui n'a pas été «déchiquetée» dans les colonnes de la table d'audit.

Étape 3: attachez la procédure à la file d'attente:

alter queue dbm_notifications_queue
with activation (
    status=on,
    procedure_name = [dbm_notifications_procedure],
    max_queue_readers = 1,
    execute as  owner);

Je devrais donc faire cela dans les deux partenaires, non? En cas de défaillance du Principal sans témoin, existe-t-il un moyen de traiter / vérifier la file d'attente? savoir si j'ai accès à toutes les situations de changement d'état, ou s'il y a quelque chose qui n'a pas été enregistré dans ma table de notifications
RagnaRock

Vous devriez le faire sur les deux partenaires, à droite. En cas d'échec sur le principal si msdbest toujours en ligne (c'est-à-dire que l'échec est une défaillance de la base de données, pas une défaillance du serveur), le traitement de la file d'attente se produira.
Remus Rusanu

Merci pour le prix. À tout le moins, vous avez maintenant une copie de "Pro SQL Server 2008 Mirroring" qui, selon moi, est un bon livre sur le sujet.
Remus Rusanu

9

J'ai dû acheter "Pro SQL Server 2008 Mirroring" après avoir lu le chapitre 6, j'ai découvert que les étapes à suivre sont les suivantes:

vérifier si le courtier de services est activé

SELECT CASE is_broker_enabled
WHEN 1 Then 'Enabled'
ELSE 'Disabled'
END
FROM sys.databases
WHERE name = 'DataBaseName'

sinon, exécutez

ALTER DATABASE DataBaseName set ENABLE_BROKER;

créer la procédure stockée que nous voulons déclencher lorsque l'événement de notification arrive:

CREATE PROCEDURE dbo.dba_MirroringStateChanged
AS
DECLARE @Message XML,
        @DBName sysname,
        @MirrorStateChange INT,
        @ServerName sysname,
        @PostTime datetime,
        @SPID INT,
        @TextData NVARCHAR(500),
        @DatabaseID INT,
        @TransactionsID INT,
        @StartTime datetime;
SET NOCOUNT ON;
-- Receive first unread message in service broker queue
RECEIVE TOP (1)
@Message = CAST(message_body AS XML)
FROM DBMirrorQueue;

BEGIN TRY
    -- Parse state change and database affected
    -- 7 or 8 = database failing over,
    --11 = synchronizing,
    --1 or 2 = synchronized
    SET @MirrorStateChange =
    @Message.value('(/EVENT_INSTANCE/State)[1]', 'int');
    SET @DBName =
    @Message.value('(/EVENT_INSTANCE/DatabaseName)[1]', 'sysname');
    SET @ServerName =
    @Message.value('(/EVENT_INSTANCE/ServerName)[1]', 'sysname');
    SET @PostTime =
    @Message.value('(/EVENT_INSTANCE/PostTime)[1]', 'datetime');
    SET @SPID = @Message.value('(/EVENT_INSTANCE/SPID)[1]', 'int');
    SET @TextData =
    @Message.value('(/EVENT_INSTANCE/TextData)[1]', 'nvarchar(500)');
    SET @DatabaseID =
    @Message.value('(/EVENT_INSTANCE/DatabaseID)[1]', 'int');
    SET @TransactionsID =
    @Message.value('(/EVENT_INSTANCE/TransactionsID)[1]', 'int');
    SET @StartTime =
    @Message.value('(/EVENT_INSTANCE/StartTime)[1]', 'datetime');
END TRY
    BEGIN CATCH
        PRINT 'Parse of mirroring state change message failed.';
    END CATCH

IF (@MirrorStateChange IN (1,2,3,4,5,6,7,8,9,10,11,12,13))
BEGIN

    DECLARE @state AS varchar(50);
    SELECT @state = mirroring_state_desc FROM SYS.database_mirroring WHERE mirroring_guid IS NOT NULL;
    IF (@state IS null) SET @state = ' ';
    INSERT INTO MirroringAlerts (DateTime, alertID, alertDesc, Sync, alertCreator) values (SYSDATETIME(), @MirrorStateChange , @TextData , @state, @SERVERNAME);

END

créer une file d'attente, pour être une sorte d'intermédiaire entre le service et la procédure stockée que nous voulons déclencher

-- Create Queue if not exists
IF NOT EXISTS (SELECT 1
    FROM sys.service_queues
    WHERE name = 'DBMirrorQueue')
BEGIN
    CREATE QUEUE DBMirrorQueue
    WITH ACTIVATION (
    PROCEDURE_NAME = dbo.dba_MirroringStateChanged,
    MAX_QUEUE_READERS = 1,
    EXECUTE AS OWNER);
END

créer le service qui sera associé à l'événement

-- Create Service if not exists
IF NOT EXISTS (SELECT 1
    FROM sys.services
    WHERE name = 'DBMirrorService')
BEGIN
    CREATE SERVICE DBMirrorService
    ON QUEUE DBMirrorQueue ([http://schemas.microsoft.com/SQL/Notifications/PostEventNotification]);
END

Créer un itinéraire

-- Create Route if not exists
IF NOT EXISTS (SELECT 1
    FROM sys.routes
    WHERE name = 'DBMirrorRoute')
BEGIN
    CREATE ROUTE DBMirrorRoute
    WITH SERVICE_NAME = 'DBMirrorService',
    ADDRESS = 'Local';
END

puis créez la notification d'événement

-- Create Event Notification if not exists
IF NOT EXISTS (SELECT 1
    FROM sys.server_event_notifications
    WHERE name = 'DBMirrorStateChange')
BEGIN
    CREATE EVENT NOTIFICATION DBMirrorStateChange
    ON SERVER
    FOR DATABASE_MIRRORING_STATE_CHANGE
    TO SERVICE 'DBMirrorService', 'current database';
END
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.