Passaggio 1: creare un servizio per ricevere le notifiche e una coda per esso:
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
Nota che sto usando msdb
, questo non è un incidente. Poiché le notifiche degli eventi a livello di server vengono inviate da msdb
esso è molto meglio se si crea anche l'endpoint di conversazione opposto (la destinazione) msdb
, il che implica che anche il servizio di destinazione e la coda devono essere distribuiti msdb
.
Passaggio 2: creare la procedura di elaborazione della notifica dell'evento:
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
La procedura di broker del servizio di scrittura non è il codice run-of-the-mill. Bisogna seguire determinati standard ed è molto facile allontanarsi nel territorio delle sabbie mobili. Questo codice mostra alcune buone pratiche:
- avvolgere il messaggio dequeue e l'elaborazione in una transazione. Nessun problema, ovvio.
- controlla sempre il tipo di messaggio ricevuto. Una buona procedura di broker di servizi deve gestire
Error
e EndDialog
messaggi in modo appropriato chiudendo la finestra di dialogo dal suo lato. Non farlo si traduce in perdite di handle ( sys.conversation_endpoints
cresce)
- controlla sempre se un messaggio è stato rimesso in coda da RECEIVE. Alcuni esempi controllano @@ rowcount dopo
RECEIVE
, il che è perfettamente OK. Questo codice di esempio si basa sulla verifica del nome del messaggio (nessun messaggio implica il nome del tipo di messaggio NULL) e gestisce implicitamente quel caso.
- creare una tabella degli errori di elaborazione. la natura di base delle procedure attivate da SSB rende davvero difficile la risoluzione degli errori se i messaggi svaniscono semplicemente senza traccia.
Inoltre, questo codice fa anche un codice di buone pratiche per quanto riguarda l'attività a portata di mano (monitoraggio DBM):
- distinguere tra
post_time
( quando è stata inviata la notifica? ), start_time
( quando è iniziata l'azione che ha attivato la notifica? ) e processing_time
( quando è stata elaborata la notifica? ). post_time
e start_time
sarà probabilmente identico o molto vicino, ma a distanza di processing_time
secondi, ore, giorni post_time
. quello interessante per l'audit è di solito post_time
.
- dal momento che l'
post_time
e processing_time
sono diversi, dovrebbe essere evidente che un DBM monitoraggio compito in una notifica anche attivato procedura non ha commercio guardando sys.database_mirroring
vista . Tale vista mostrerà lo stato corrente al momento dell'elaborazione, che può essere o meno correlato all'evento. Se l'elaborazione si verifica molto tempo dopo la pubblicazione dell'evento (si ritiene che i tempi di inattività della manutenzione) siano evidenti, il problema è evidente, ma può gestire anche l'elaborazione "integra" se il DBM cambia lo stato molto velocemente e registra due (o più) eventi in un riga (che accade di frequente): in questa situazione l'elaborazione, come nel codice che hai pubblicato, controlla l'evento nel momento in cui si verifica, ma registra lo stato corrente, finale ,. Leggere un simile audit potrebbe essere molto confuso in seguito.
- controlla sempre l'evento XML originale. In questo modo è possibile in seguito interrogare questo XML per qualsiasi informazione che non sia stata "distrutta" in colonne nella tabella di controllo.
Passaggio 3: allegare la procedura alla coda:
alter queue dbm_notifications_queue
with activation (
status=on,
procedure_name = [dbm_notifications_procedure],
max_queue_readers = 1,
execute as owner);