Bước 1: Tạo một dịch vụ để nhận thông báo và xếp hàng cho nó:
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
Lưu ý rằng tôi đang sử dụng msdb
, đây không phải là một tai nạn. Bởi vì các thông báo sự kiện ở cấp máy chủ được gửi từ msdb
nó sẽ tốt hơn nhiều nếu bạn tạo điểm cuối cuộc trò chuyện ngược lại (mục tiêu) msdb
, điều này ngụ ý rằng dịch vụ đích và hàng đợi cũng phải được triển khai msdb
.
Bước 2: tạo quy trình xử lý thông báo sự kiện:
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
Viết thủ tục môi giới dịch vụ không phải là mã chạy của bạn. Người ta phải tuân theo các tiêu chuẩn nhất định và rất dễ đi lạc vào lãnh thổ cát lún. Mã này cho thấy một số thực tiễn tốt:
- bọc dequeue tin nhắn và xử lý trong một giao dịch. Không có trí tuệ, rõ ràng.
- luôn luôn kiểm tra loại tin nhắn nhận được. Một thủ tục môi giới dịch vụ tốt phải xử lý
Error
và EndDialog
nhắn tin một cách thích hợp bằng cách kết thúc hộp thoại từ phía đó. Không làm như vậy dẫn đến xử lý rò rỉ ( sys.conversation_endpoints
tăng trưởng)
- luôn luôn kiểm tra nếu một tin nhắn đã được xử lý bằng cách NHẬN. Một số mẫu kiểm tra @@ rowcount sau
RECEIVE
, điều này hoàn toàn OK. Mã mẫu này dựa trên kiểm tra tên tin nhắn (không có tin nhắn ngụ ý tên loại tin nhắn NULL) và xử lý trường hợp đó hoàn toàn.
- tạo một bảng lỗi xử lý. bản chất nền của các thủ tục kích hoạt SSB làm cho việc khắc phục lỗi rất khó khăn nếu các thông báo đơn giản biến mất dấu vết.
Ngoài ra, mã này cũng thực hiện một số mã thực hành tốt liên quan đến nhiệm vụ hiện tại (giám sát DBM):
- phân biệt giữa
post_time
( khi nào thông báo được gửi? ), start_time
( khi nào thì hành động kích hoạt bắt đầu đáng chú ý? ) và processing_time
( khi nào thông báo được xử lý? ). post_time
và start_time
có thể sẽ giống hệt hoặc rất gần, nhưng processing_time
có thể cách nhau vài giây, vài giờ, vài ngày post_time
. một trong những thú vị cho kiểm toán thường là post_time
.
- vì
post_time
và processing_time
khác nhau, rõ ràng là một nhiệm vụ giám sát DBM trong một quy trình kích hoạt thông báo thậm chí không có doanh nghiệp nhìn vào sys.database_mirroring
xem . Quan điểm đó sẽ hiển thị hiện tại trạng thái tại tại thời điểm xử lý, có thể có hoặc không liên quan đến sự kiện. Nếu quá trình xử lý xảy ra trong một thời gian dài sau khi sự kiện được đăng (nghĩ rằng thời gian ngừng bảo trì) thì vấn đề là rõ ràng, nhưng nó cũng có thể xử lý trong quá trình xử lý 'lành mạnh' nếu DBM thay đổi trạng thái rất nhanh và đăng hai (hoặc nhiều) sự kiện trong một hàng (xảy ra thường xuyên): trong tình huống này, việc xử lý, như trong mã bạn đã đăng, kiểm tra sự kiện khi chúng xảy ra, nhưng sẽ ghi lại trạng thái hiện tại, cuối cùng . Đọc một cuộc kiểm toán như vậy có thể rất khó hiểu sau này.
- luôn kiểm toán sự kiện XML gốc. Bằng cách này, sau này bạn có thể truy vấn XML này cho bất kỳ thông tin nào không được 'băm nhỏ' thành các cột trong bảng kiểm toán.
Bước 3: đính kèm thủ tục vào hàng đợi:
alter queue dbm_notifications_queue
with activation (
status=on,
procedure_name = [dbm_notifications_procedure],
max_queue_readers = 1,
execute as owner);