Paso 1: cree un servicio para recibir las notificaciones y una cola para ello:
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
Tenga en cuenta que estoy usando msdb
, esto no es un accidente. Debido a que las notificaciones de eventos a nivel de servidor se envían desde msdb
él, es mucho mejor si crea el punto final de conversación opuesto (el destino) también msdb
, lo que implica que el servicio de destino y la cola también deben implementarse msdb
.
Paso 2: cree el procedimiento de procesamiento de notificaciones 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
Escribir el procedimiento de Service Broker no es su código habitual. Uno debe seguir ciertos estándares y es muy fácil desviarse en arenas movedizas. Este código muestra algunas buenas prácticas:
- ajusta la cola del mensaje y el procesamiento en una transacción. No es obvio, obvio.
- siempre verifique el tipo de mensaje recibido. Un buen procedimiento de intermediario de servicios debe manejar
Error
y EndDialog
enviar mensajes adecuadamente al finalizar el diálogo desde su lado. No hacerlo da como resultado fugas en el mango ( sys.conversation_endpoints
crece)
- siempre verifique si un mensaje fue cancelado por RECEIVE. Algunas muestras comprueban @@ rowcount después
RECEIVE
, lo cual está perfectamente bien. Este código de muestra se basa en la verificación del nombre del mensaje (ningún mensaje implica un nombre de tipo de mensaje NULL) y maneja ese caso implícitamente.
- crear una tabla de errores de procesamiento. La naturaleza de fondo de los procedimientos activados por SSB hace que sea realmente difícil solucionar errores si los mensajes simplemente desaparecen sin dejar rastro.
Además, este código también incluye código de buenas prácticas con respecto a la tarea en cuestión (monitoreo de DBM):
- diferenciar entre
post_time
(¿ cuándo se envió la notificación? ), start_time
(¿ cuándo comenzó la acción que activó la notificación? ) y processing_time
(¿ cuándo se procesó la notificación? ). post_time
y start_time
es probable que sean idénticos o muy cercanos, pero processing_time
pueden ser segundos, horas, días separados post_time
. El interesante para la auditoría es generalmente post_time
.
- Dado que el
post_time
y el processing_time
son diferentes, debería ser obvio que una tarea de monitoreo de DBM en un procedimiento activado de notificación uniforme no tiene nada que sys.database_mirroring
ver . Esa vista mostrará el estado actual en el momento del procesamiento, que puede o no estar relacionado con el evento. Si el procesamiento se produce mucho tiempo después de la publicación del evento (piense en el tiempo de inactividad por mantenimiento), el problema es obvio, pero también se puede manejar en el procesamiento 'saludable' si el DBM cambia de estado muy rápido y publica dos (o más) eventos en un fila (que ocurre con frecuencia): en esta situación, el procesamiento, como en el código que publicó, audita el evento a medida que ocurre, pero registrará el estado actual final . Leer una auditoría de este tipo podría ser muy confuso más tarde.
- audite siempre el evento XML original. De esta manera, puede consultar este XML en busca de información que no se haya "desmenuzado" en columnas en la tabla de auditoría.
Paso 3: adjunte el procedimiento a la cola:
alter queue dbm_notifications_queue
with activation (
status=on,
procedure_name = [dbm_notifications_procedure],
max_queue_readers = 1,
execute as owner);