Como criar uma notificação de evento que executa uma tarefa / procedimento ao espelhar alterações de estado

11

Estou fazendo esta pergunta na sequência desta. Posso enviar uma string sobre TCP usando T-SQL?

Remus Rusanu expõe o que parece ser uma solução ideal para o meu problema, mas ... sou imaturo demais para entender e fazer tudo o que ele diz.

Até agora, acho que o que preciso para criar um evento de notificação para DATABASE_MIRRORING_STATE_CHANGE, estou correto?

como posso criar essa notificação de evento para quando seu gatilho insere uma linha em uma tabela, que armazena um carimbo de data e hora e um ID que vem da notificação.

até agora, estou configurando um alerta por ID, cada um executando um trabalho como este (este exemplo é para 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)

Basicamente, estou criando um log interno nesse banco de dados:

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]

Mas dessa forma ... os alertas não estão sendo acionados rápido o suficiente ... então estou perdendo informações ...

Você pode me dizer como programar esse comportamento com a notificação de criação de evento para o evento Alterado pelo Estado do Espelhamento de Banco de Dados ?

Cumprimentos

RagnaRock
fonte

Respostas:

13

Etapa 1: crie um serviço para receber as notificações e uma fila para ele:

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

Note que estou usando msdb, isso não é um acidente. Como as notificações de eventos no nível do servidor são enviadas msdb, é muito melhor se você criar o ponto de extremidade de conversa oposto (o destino) também msdb, o que implica que o serviço de destino e a fila também devem ser implantados msdb.

Etapa 2: crie o procedimento de processamento de notificação de eventos:

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

O procedimento do agente de gravação de serviços não é o seu código comum. É preciso seguir certos padrões e é muito fácil desviar-se para o território de areia movediça. Este código mostra algumas boas práticas:

  • agrupar a desenfileiramento da mensagem e o processamento em uma transação. Não é óbvio.
  • sempre verifique o tipo de mensagem recebida. Um bom procedimento do intermediário de serviço deve manipular Errore enviar EndDialogmensagens adequadamente, encerrando a caixa de diálogo pelo lado. Não fazer isso resulta em vazamentos de identificador ( sys.conversation_endpointscresce)
  • sempre verifique se uma mensagem foi removida da fila por RECEIVE. Algumas amostras marcam @@ rowcount depois RECEIVE, o que é perfeitamente aceitável. Este código de exemplo baseia-se na verificação do nome da mensagem (nenhuma mensagem implica o nome do tipo de mensagem NULL) e lida com esse caso implicitamente.
  • crie uma tabela de erros de processamento. a natureza de segundo plano dos procedimentos ativados por SSB dificulta muito a solução de erros se as mensagens simplesmente desaparecerem sem rastreamento.

Além disso, esse código também faz um código de boas práticas com relação à tarefa em questão (monitoramento do DBM):

  • diferencie entre post_time( quando a notificação foi enviada? ), start_time( quando a ação que acionou a notificação começou? ) e processing_time( quando a notificação foi processada? ). post_timee start_timeprovavelmente será idêntico ou muito próximo, mas processing_timepoderá demorar alguns segundos, horas e dias post_time. o interessante para a auditoria é geralmente post_time.
  • como the post_timee the processing_timesão diferentes, deve ser óbvio que uma tarefa de monitoramento do DBM em um procedimento ativado por notificação uniforme não tem nada a sys.database_mirroringver . Essa visualização mostrará o estado atual no momento do processamento, que pode ou não estar relacionado ao evento. Se o processamento ocorrer muito tempo após a postagem do evento (pense em tempo de inatividade para manutenção), o problema será óbvio, mas ele também poderá lidar com o processamento 'íntegro' se o DBM mudar de estado muito rapidamente e postar dois (ou mais) eventos em um linha (o que acontece frequentemente): nesta situação o processamento, como no código que você postou, auditar o evento como eles ocorrem, mas irá gravar a corrente, último , estado. Ler essa auditoria pode ser muito confuso mais tarde.
  • sempre audite o evento XML original. Dessa forma, você poderá consultar posteriormente esse XML para obter informações que não foram 'fragmentadas' em colunas na tabela de auditoria.

Etapa 3: anexar o procedimento à fila:

alter queue dbm_notifications_queue
with activation (
    status=on,
    procedure_name = [dbm_notifications_procedure],
    max_queue_readers = 1,
    execute as  owner);
Remus Rusanu
fonte
Então, devo fazer isso nos dois parceiros, certo? Em caso de falha no diretor sem testemunha, existe uma maneira de processar / verificar a fila? para saber se tenho acesso a todas as situações de mudança de estado, ou se há algo que não tenha sido registrado em minha mesa notificações
Ragnarock
Você deve fazer isso nos dois parceiros, certo. Em caso de falha no Principal, se msdbainda estiver on-line (ou seja, a falha é uma falha do DB, não uma falha do servidor), o processamento da fila ocorrerá.
Remus Rusanu 12/01
Obrigado pelo prêmio. No mínimo, agora você tem uma cópia do "Pro SQL Server 2008 Mirroring", que eu ouvi dizer que é um bom livro sobre o assunto.
Remus Rusanu 12/01
9

Eu tive que comprar o "Pro SQL Server 2008 Mirroring" depois de ler o capítulo 6, e descobri as etapas para fazer isso:

verifique se o service broker está ativado

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

se não, corra

ALTER DATABASE DataBaseName set ENABLE_BROKER;

crie o procedimento armazenado que queremos que seja acionado quando o evento de notificação chegar:

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

criar uma fila, para ser algum tipo de intermediário entre o serviço e o procedimento armazenado que queremos acionar

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

crie o serviço que será associado ao evento

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

Crie uma rota

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

e crie a notificação de eventos

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