Langkah 1: Buat layanan untuk menerima notifikasi dan antrian untuk itu:
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
Perhatikan bahwa saya menggunakan msdb
, ini bukan kecelakaan. Karena pemberitahuan peristiwa tingkat server dikirim dari msdb
sana, jauh lebih baik jika Anda membuat titik akhir percakapan yang berlawanan (target) juga msdb
, yang menyiratkan bahwa layanan tujuan dan antrian juga harus digunakan msdb
.
Langkah 2: buat prosedur pemrosesan pemberitahuan acara:
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
Prosedur broker layanan penulisan bukanlah kode run-of-the-mill Anda. Seseorang harus mengikuti standar tertentu dan sangat mudah menyimpang ke wilayah pasir isap. Kode ini menunjukkan beberapa praktik yang baik:
- bungkus dequeue pesan dan pemrosesan dalam transaksi. Tidak punya otak, jelas.
- selalu periksa jenis pesan yang diterima. Prosedur broker layanan yang baik harus menangani
Error
dan EndDialog
pesan dengan tepat dengan mengakhiri dialog dari sisi itu. Tidak melakukan hal itu mengakibatkan kebocoran pegangan ( sys.conversation_endpoints
tumbuh)
- selalu periksa apakah ada pesan yang keluar dari RECEIVE. Beberapa sampel memeriksa @@ rowcount setelah
RECEIVE
, yang sangat OK. Kode sampel ini bergantung pada pemeriksaan nama pesan (tidak ada pesan yang menyiratkan nama jenis pesan NULL) dan menangani kasus tersebut secara implisit.
- buat tabel kesalahan pemrosesan. sifat latar belakang prosedur yang diaktifkan SSB membuatnya sangat sulit untuk memecahkan kesalahan jika pesan hilang begitu saja tanpa jejak.
Selain itu, kode ini juga melakukan beberapa kode praktik baik terkait tugas yang ada (memantau DBM):
- bedakan antara
post_time
( kapan pemberitahuan dikirim? ), start_time
( kapan tindakan yang memicu pemberitahuan dimulai? ) dan processing_time
( kapan pemberitahuan diproses? ). post_time
dan start_time
kemungkinan akan identik atau sangat dekat, tetapi processing_time
dapat menjadi detik, jam, hari terpisah dari post_time
. yang menarik untuk audit biasanya post_time
.
- karena
post_time
dan processing_time
berbeda, harus jelas bahwa tugas pemantauan DBM dalam prosedur yang diaktifkan notifikasi bahkan tidak memiliki bisnis sys.database_mirroring
melihat . Pandangan itu akan menunjukkan keadaan saat ini pada saat pemrosesan, yang mungkin atau mungkin tidak terkait dengan acara tersebut. Jika pemrosesan terjadi lama setelah acara diposting (pikirkan waktu henti pemeliharaan) daripada masalahnya jelas, tetapi dapat menangani juga dalam pemrosesan 'sehat' jika perubahan DBM menyatakan sangat cepat dan memposting dua (atau lebih) peristiwa dalam baris (yang sering terjadi): dalam situasi ini pemrosesan, seperti dalam kode yang Anda poskan, mengaudit peristiwa saat itu terjadi, tetapi akan mencatat keadaan saat ini, final , saat ini. Membaca audit semacam itu bisa sangat membingungkan nantinya.
- selalu mengaudit acara XML asli. Dengan cara ini nanti Anda dapat meminta XML ini untuk informasi apa pun yang tidak 'robek' ke dalam kolom di tabel audit.
Langkah 3: lampirkan prosedur ke antrian:
alter queue dbm_notifications_queue
with activation (
status=on,
procedure_name = [dbm_notifications_procedure],
max_queue_readers = 1,
execute as owner);