So erstellen Sie eine Ereignisbenachrichtigung, die beim Spiegeln von Statusänderungen einen Job / eine Prozedur ausführt

11

Ich stelle diese Frage in der folgenden Reihenfolge. Kann ich mit T-SQL eine Zeichenfolge über TCP senden?

Remus Rusanu enthüllt, was für mein Problem eine optimale Lösung zu sein scheint, aber ... ich bin zu unreif, um alles zu verstehen und zu machen, was er sagt.

Bisher denke ich, was ich brauche, um ein Benachrichtigungsereignis für DATABASE_MIRRORING_STATE_CHANGE zu erstellen. Bin ich richtig?

Wie kann ich diese Ereignisbenachrichtigung erstellen, wenn sie ausgelöst wird und eine Zeile in eine Tabelle einfügt, in der ein Zeitstempel und eine ID gespeichert sind, die aus der Benachrichtigung stammen?

Bisher richte ich eine Warnung pro ID ein, von der jede einen Job wie diesen ausführt (dieses Beispiel gilt für 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)

Grundsätzlich erstelle ich ein internes Protokoll in dieser Datenbank:

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]

Aber auf diese Weise ... werden die Warnungen nicht schnell genug ausgelöst ... also verliere ich Informationen ...

Können Sie mir sagen , wie dieses Verhalten zu programmieren , mit Ereignisbenachrichtigung erstellen für Database Mirroring Zustand geändert Ereignis ?

Freundliche Grüße

RagnaRock
quelle

Antworten:

13

Schritt 1: Erstellen Sie einen Dienst zum Empfangen der Benachrichtigungen und eine Warteschlange dafür:

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

Beachten Sie, dass ich benutze msdb, dies ist kein Unfall. Da die Ereignisbenachrichtigungen auf Serverebene von dort gesendet werden msdb, ist es viel besser, wenn Sie den entgegengesetzten Konversationsendpunkt (das Ziel) auch in erstellen msdb, was bedeutet, dass der Zieldienst und die Warteschlange auch in bereitgestellt werden müssen msdb.

Schritt 2: Erstellen Sie die Prozedur zur Verarbeitung von Ereignisbenachrichtigungen:

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

Das Schreiben einer Service Broker-Prozedur ist kein gewöhnlicher Code. Man muss bestimmte Standards befolgen und ist sehr leicht in Treibsandgebiet abzuwandern. Dieser Code zeigt einige bewährte Methoden:

  • Schließen Sie die Nachrichten-Warteschlange und die Verarbeitung in eine Transaktion ein. Kein Problem, offensichtlich.
  • Überprüfen Sie immer den empfangenen Nachrichtentyp. Ein guter Service Broker Verfahren muss verarbeiten Errorund EndDialogNachrichten in geeigneter Weise durch den Dialog von der Seite liegend endet. Andernfalls kommt es zu Grifflecks ( sys.conversation_endpointswächst)
  • Überprüfen Sie immer, ob eine Nachricht von RECEIVE aus der Warteschlange entfernt wurde. Einige Beispiele überprüfen @@ rowcount nach RECEIVE, was vollkommen in Ordnung ist. Dieser Beispielcode basiert auf der Überprüfung des Nachrichtennamens (keine Nachricht impliziert den Namen des NULL-Nachrichtentyps) und behandelt diesen Fall implizit.
  • Erstellen Sie eine Verarbeitungsfehlertabelle. Der Hintergrund von SSB-aktivierten Prozeduren macht es wirklich schwierig, Fehler zu beheben, wenn die Nachrichten einfach ohne Ablaufverfolgung verschwinden.

Außerdem führt dieser Code auch einige bewährte Codes für die jeweilige Aufgabe aus (Überwachung von DBM):

  • Unterscheiden Sie zwischen post_time( wann wurde die Benachrichtigung gesendet? ), start_time( wann wurde die Aktion ausgelöst, die die Benachrichtigung ausgelöst hat? ) und processing_time( wann wurde die Benachrichtigung verarbeitet? ). post_timeund start_timewird wahrscheinlich identisch oder sehr nahe sein, processing_timekann aber Sekunden, Stunden, Tage voneinander entfernt sein post_time. Das Interessante für die Prüfung ist normalerweise post_time.
  • Da die post_timeund die processing_timeunterschiedlich sind, sollte es offensichtlich sein, dass eine DBM-Überwachungsaufgabe in einer Prozedur mitsys.database_mirroring gleichmäßiger Benachrichtigung kein Geschäft hat, das die Ansicht betrachtet . Diese Ansicht zeigt den aktuellen Status zum Zeitpunkt der Verarbeitung an, der möglicherweise mit dem Ereignis zusammenhängt oder nicht. Wenn die Verarbeitung lange nach dem Veröffentlichen des Ereignisses erfolgt (denken Sie an Wartungsausfälle), ist das Problem offensichtlich, kann aber auch bei einer "fehlerfreien" Verarbeitung behandelt werden, wenn der DBM-Status sehr schnell ändert und zwei (oder mehr) Ereignisse in einem Ereignis veröffentlicht werden Zeile (was häufig vorkommt): in dieser Situation die Verarbeitung, wie in dem Code , den Sie geschrieben, das Ereignis prüfen , wie sie auftreten, sondern wird den aktuellen, notiert endgültig , Zustand. Das Lesen eines solchen Audits kann später sehr verwirrend sein.
  • Überprüfen Sie immer das ursprüngliche XML-Ereignis. Auf diese Weise können Sie dieses XML später nach Informationen abfragen, die nicht in Spalten in der Prüftabelle "zerlegt" wurden.

Schritt 3: Hängen Sie die Prozedur an die Warteschlange an:

alter queue dbm_notifications_queue
with activation (
    status=on,
    procedure_name = [dbm_notifications_procedure],
    max_queue_readers = 1,
    execute as  owner);
Remus Rusanu
quelle
Also sollte ich das bei beiden Partnern richtig machen? Gibt es eine Möglichkeit, die Warteschlange zu bearbeiten / zu überprüfen, wenn der Auftraggeber ohne Zeugen versagt? um zu wissen, ob ich Zugriff auf alle Situationen mit Änderungsstatus habe oder ob etwas in meiner Benachrichtigungstabelle nicht protokolliert wurde
RagnaRock
Sie sollten es auf beiden Partnern tun, richtig. Im Falle eines Fehlers auf dem Principal, wenn dieser msdbnoch online ist (dh der Fehler ist ein DB-Fehler, kein Serverfehler), erfolgt die Warteschlangenverarbeitung.
Remus Rusanu
Danke für die Auszeichnung. Zumindest haben Sie jetzt eine Kopie von "Pro SQL Server 2008 Mirroring", von der ich höre, dass sie ein gutes Buch zu diesem Thema ist.
Remus Rusanu
9

Nachdem ich Kapitel 6 gelesen hatte, musste ich "Pro SQL Server 2008 Mirroring" kaufen. Die folgenden Schritte wurden ausgeführt:

Überprüfen Sie, ob der Service Broker aktiviert ist

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

Wenn nicht, laufen

ALTER DATABASE DataBaseName set ENABLE_BROKER;

Erstellen Sie die gespeicherte Prozedur, die ausgelöst werden soll, wenn das Benachrichtigungsereignis eintrifft:

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

Erstellen Sie eine Warteschlange, um eine Art Vermittler zwischen dem Dienst und der gespeicherten Prozedur zu sein, die wir auslösen möchten

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

Erstellen Sie den Dienst, der dem Ereignis zugeordnet wird

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

Erstellen Sie eine Route

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

und erstellen Sie dann die Ereignisbenachrichtigung

-- 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
RagnaRock
quelle