ขั้นตอนที่ 1: สร้างบริการเพื่อรับการแจ้งเตือนและคิว:
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
โปรดทราบว่าฉันกำลังใช้msdb
นี่ไม่ใช่อุบัติเหตุ เนื่องจากการแจ้งเตือนเหตุการณ์ระดับเซิร์ฟเวอร์msdb
นั้นดีกว่ามากถ้าคุณสร้างจุดสิ้นสุดการสนทนาตรงข้าม (เป้าหมาย) ด้วยmsdb
ซึ่งหมายความว่าบริการปลายทางและคิวต้องถูกปรับใช้msdb
ด้วย
ขั้นตอนที่ 2: สร้างโพรซีเดอร์การประมวลผลการแจ้งเตือนเหตุการณ์:
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
ขั้นตอนนายหน้าบริการการเขียนไม่ใช่รหัสที่คุณใช้ หนึ่งจะต้องปฏิบัติตามมาตรฐานบางอย่างและเป็นเรื่องง่ายมากที่จะหลงทางในดินแดนทรายดูด รหัสนี้แสดงแนวทางปฏิบัติที่ดีบางประการ:
- ห่อข้อความและการประมวลผลในธุรกรรม ไม่มีเกมง่ายๆชัดเจน
- ตรวจสอบประเภทข้อความที่ได้รับเสมอ ขั้นตอนนายหน้าบริการที่ดีจะต้องจัดการ
Error
และEndDialog
ส่งข้อความอย่างเหมาะสมโดยสิ้นสุดการโต้ตอบจากด้านข้าง ไม่ทำเช่นนั้นส่งผลให้เกิดการรั่วไหล ( sys.conversation_endpoints
ขึ้น)
- ตรวจสอบเสมอว่าข้อความถูก dequeued โดยรับ ตัวอย่างบางส่วนตรวจสอบ @@ rowcount หลังจาก
RECEIVE
ที่ตกลงอย่างสมบูรณ์ โค้ดตัวอย่างนี้อาศัยการตรวจสอบชื่อข้อความ (ไม่มีข้อความที่บอกเป็นนัยถึงชื่อประเภทข้อความ NULL) และจัดการกับกรณีนั้นโดยปริยาย
- สร้างตารางข้อผิดพลาดในการประมวลผล ลักษณะพื้นหลังของโพรซีเดอร์ที่เปิดใช้งาน SSB ทำให้ยากต่อการแก้ไขข้อผิดพลาดหากข้อความหายไปโดยไม่มีการติดตาม
นอกจากนี้รหัสนี้ยังทำโค้ดแนวปฏิบัติที่เหมาะสมเกี่ยวกับงานที่กำลังทำอยู่ (การตรวจสอบ DBM):
- แยกความแตกต่างระหว่าง
post_time
( เมื่อมีการส่งการแจ้งเตือน ), start_time
( เมื่อใดที่การกระทำที่เริ่มการแจ้งเตือนเริ่มขึ้น ) และprocessing_time
( เมื่อการแจ้งเตือนถูกประมวลผลเมื่อใด ) post_time
และstart_time
มีแนวโน้มที่จะเหมือนกันหรือใกล้มาก แต่สามารถวินาทีชั่วโมงวันนอกเหนือจากprocessing_time
หนึ่งที่น่าสนใจสำหรับการตรวจสอบเป็นเรื่องปกติpost_time
post_time
- ตั้งแต่
post_time
และprocessing_time
จะแตกต่างกันก็ควรจะชัดเจนว่า DBM การตรวจสอบงานในขั้นตอนการเปิดใช้งานการแจ้งเตือนแม้มีธุรกิจไม่มองไปที่sys.database_mirroring
มุมมอง มุมมองนั้นจะแสดงสถานะปัจจุบันในขณะที่ทำการประมวลผลซึ่งอาจหรืออาจจะไม่เกี่ยวข้องกับเหตุการณ์ หากการประมวลผลเกิดขึ้นเป็นเวลานานหลังจากเหตุการณ์ถูกโพสต์ (คิดว่าการหยุดทำงานของการบำรุงรักษา) เกินกว่าปัญหาจะเห็นได้ชัด แต่สามารถจัดการกับการประมวลผลที่ 'ดีต่อสุขภาพ' ได้ถ้า DBM เปลี่ยนสถานะอย่างรวดเร็วและโพสต์สองเหตุการณ์ แถว (ซึ่งเกิดขึ้นบ่อยครั้ง): ในสถานการณ์นี้การประมวลผลเช่นเดียวกับในรหัสที่คุณโพสต์ให้ตรวจสอบเหตุการณ์ที่เกิดขึ้น แต่จะบันทึกสถานะปัจจุบันขั้นสุดท้ายสถานะ การอ่านการตรวจสอบดังกล่าวอาจทำให้สับสนในภายหลัง
- ตรวจสอบเหตุการณ์ XML ดั้งเดิมเสมอ วิธีนี้คุณสามารถสืบค้น XML นี้ในภายหลังสำหรับข้อมูลใด ๆ ที่ไม่ได้ 'ฉีก' เป็นคอลัมน์ในตารางการตรวจสอบ
ขั้นตอนที่ 3: แนบขั้นตอนกับคิว:
alter queue dbm_notifications_queue
with activation (
status=on,
procedure_name = [dbm_notifications_procedure],
max_queue_readers = 1,
execute as owner);