一.本文所涉及的內容(Contents)
- 本文所涉及的內容(Contents)
- 背景(Contexts)
- 基礎知識(Rudimentary Knowledge)
- 事件通知監控DDL(NotifyQueue_DDL)
- 事件通知監控SQL跟蹤事件(NotifyQueue_Trace)
- 注意事項(Attention)
- 疑問(Questions)
- 參考文獻(References)
二.背景(Contexts)
SQL Server事件通知有什么用呢?如果你想監控SQL Server的DDL操作,你可以通過DDL觸發器(參考:SQL Server DDL觸發器運用),也可以通過SQL Server 事件通知把這個事件相關的信息發送到 Service Broker 服務;他們最大的區別就是DDL觸發器可以進行ROLLBACK,而事件通知不行;還有,事件通知是異步發送消息的;
SQL Server 事件通知還可以響應部分SQL跟蹤事件,即SQL Trace (參考:SQL Server 默認跟蹤(Default Trace)、SQL Server 創建跟蹤);他們最大的區別就是跟蹤事件可以自定義生成哪些數據列,而事件通知是生成固定的XML;還有,每次重新啟動服務器時,都必須重新啟動跟蹤。
三.基礎知識(Rudimentary Knowledge)
事件通知將有關事件的信息發送給 Service Broker 服務。執行事件通知可對各種 Transact-SQL 數據定義語言 (DDL) 語句和 SQL跟蹤事件做出響應,並將這些事件的相關信息發送到 Service Broker 服務。
事件通知可以用來執行以下操作:
- 記錄和檢索發生在數據庫上的更改或活動。
- 執行操作以異步(asynchronous)方式而不是同步方式響應事件。
可以將事件通知用作替代 DDL 觸發器和 SQL 跟蹤的編程方法,因為你可以通過讀取Service Broker 服務中的隊列,在程序中對信息進行處理。
事件信息作為 xml 類型的變量傳遞給 Service Broker 服務,它提供了有關事件的發生時間、受影響的數據庫對象、涉及的 Transact-SQL 批處理語句的信息以及其他信息。
下圖是我對事件通知邏輯關系的理解,當數據庫A或者實例B產生DDL就會激發事件通知,這個通知把相應的DDL的XML信息發送給隊列,你可以通過SQL獲取到隊列中的XML;
(Figure1:事件通知邏輯關系圖)
創建事件通知的event_type參數 可以為 Transact-SQL DDL 事件類型、SQL 跟蹤事件類型或 Service Broker 事件類型有關限定 Transact-SQL DDL 事件類型的列表,請參閱 DDL事件。 Service Broker 事件類型為 QUEUE_ACTIVATION 和 BROKER_QUEUE_DISABLED。 有關詳細信息,請參閱事件通知。
四.事件通知監控DDL(NotifyQueue_DDL)
數據庫的DDL操作默認會被記錄到Default Trace默認跟蹤中(參考:SQL Server 默認跟蹤(Default Trace)),這是一個被動式的監控;而主動式的監控就可以使用DDL觸發器(參考:SQL Server DDL觸發器運用);我們還可以使用事件通知的形式監控DDL,下面就着重講講實現過程。
下面創建一個SSB_DB數據庫,捕獲數據庫實例的DDL語句;
--Step1:創建示例數據庫 USE master GO IF EXISTS(SELECT name FROM sys.databases WHERE name = 'SSB_DB') DROP DATABASE SSB_DB GO CREATE DATABASE SSB_DB GO USE SSB_DB GO --Step2:創建隊列,默認為開啟 CREATE QUEUE NotifyQueue_DDL --WITH STATUS=ON GO --Step3:創建服務 CREATE SERVICE NotifyService_DDL ON QUEUE NotifyQueue_DDL ([http://schemas.microsoft.com/SQL/Notifications/PostEventNotification]); GO --Step4:對系統目錄視圖sys.databases進行查詢 SELECT service_broker_guid FROM sys.databases WHERE name = 'SSB_DB' /* DB19CBE8-0581-4604-B44A-812C29A565BB */ --Step5:創建事件通知,使用上面返回的GUID值 CREATE EVENT NOTIFICATION NotifyEvent_DDL ON DATABASE FOR DDL_DATABASE_LEVEL_EVENTS TO SERVICE 'NotifyService_DDL', 'DB19CBE8-0581-4604-B44A-812C29A565BB'; --Step6:測試 CREATE TABLE TestTable (a int) GO DROP TABLE TestTable; GO --Step7:使用Select或Recieve(其中Recieve會刪除隊列中的事件消息)查詢隊列 SELECT CAST(message_body as xml) EventInfo FROM dbo.NotifyQueue_DDL
上面的SQL腳本,你需要注意以下幾點:
1. 創建的EVENT NOTIFICATION是針對當前數據庫的(ON DATABASE),只有在當前數據庫發生的DDL才會被捕獲;
2. 使用DDL_DATABASE_LEVEL_EVENTS將會監控當前數據庫所有DDL事件,你可以使用DDL_SERVER_LEVEL_EVENTS監控數據庫實例的DDL操作,更多的DDL事件可以參考:DDL 事件組;
3. 關於service_broker_guid,你應該通過查詢確認這個值,它的作用是指定解析 broker_service 所依據的 Service Broker 實例。
執行Step7返回下圖的結果,這是執行Step6腳本產生的兩條DDL消息:
(Figure3:DDL事件在隊列中的XML信息)
通過下面的SQL腳本可以對Figure4所示的XML進行解釋,保存到表中:
執行Step10將返回Figu
--Step8:創建表 CREATE TABLE [dbo].[EventInfo]( [EventInfoID] [int] IDENTITY(1,1) NOT NULL, [PostTime] [datetime] NOT NULL, [ServerName] [sysname] NOT NULL, [LoginName] [sysname] NOT NULL, [DatabaseUser] [sysname] NOT NULL, [DatabaseName] [sysname] NOT NULL, [Schema] [sysname] NULL, [Object] [sysname] NULL, [TSQL] [nvarchar](max) NOT NULL, [Event] [sysname] NOT NULL, [XmlEvent] [xml] NOT NULL, CONSTRAINT [PK_EventInfo_EventInfoID] PRIMARY KEY NONCLUSTERED ( [EventInfoID] ASC ) ON [PRIMARY] ) ON [PRIMARY] --Step9:創建分離XML的存儲過程 -- ============================================= -- Author: <聽風吹雨> -- Create date: <2013.06.19> -- Description: <分離XML> -- Blog: <http://www.cnblogs.com/gaizai/> -- ============================================= CREATE PROCEDURE sp_SeparateXML AS BEGIN SET NOCOUNT ON; DECLARE @data XML DECLARE @itemCur CURSOR SET @itemCur = CURSOR FOR SELECT CAST(message_body as xml) EventInfo FROM [SSB_DB].[dbo].NotifyQueue_DDL OPEN @itemCur FETCH NEXT FROM @itemCur INTO @data WHILE @@FETCH_STATUS=0 BEGIN --邏輯處理 INSERT [SSB_DB].[dbo].[EventInfo]( [PostTime], [ServerName], [LoginName], [DatabaseUser], [DatabaseName], [Schema], [Object], [TSQL], [Event], [XmlEvent]) VALUES( @data.value('(/EVENT_INSTANCE/PostTime)[1]', 'sysname'), @data.value('(/EVENT_INSTANCE/ServerName)[1]', 'sysname'), @data.value('(/EVENT_INSTANCE/LoginName)[1]', 'sysname'), @data.value('(/EVENT_INSTANCE/UserName)[1]', 'sysname'), @data.value('(/EVENT_INSTANCE/DatabaseName)[1]', 'sysname'), @data.value('(/EVENT_INSTANCE/SchemaName)[1]', 'sysname'), @data.value('(/EVENT_INSTANCE/ObjectName)[1]', 'sysname'), @data.value('(/EVENT_INSTANCE/TSQLCommand)[1]', 'nvarchar(max)'), @data.value('(/EVENT_INSTANCE/EventType)[1]', 'sysname'), @data ); FETCH NEXT FROM @itemCur INTO @data END CLOSE @itemCur DEALLOCATE @itemCur END GO --Step10:解析XML TRUNCATE TABLE [EventInfo] EXEC dbo.sp_SeparateXML SELECT * FROM [EventInfo]
re4和Figure5的結果,這就是分解后的效果。
(Figure4:結構化XML返回列表)
(Figure5:結構化XML返回列表補充)
上面Step9是使用游標的形式獲取XML事件信息,下面Step11以RECEIVE方式獲取XML事件信息,這種形式會把消息從隊列中刪除。
--Step11:創建分離XML的存儲過程 -- ============================================= -- Author: <聽風吹雨> -- Create date: <2013.06.20> -- Description: <RECEIVE方式分離XML> -- Blog: <http://www.cnblogs.com/gaizai/> -- ============================================= CREATE PROCEDURE [dbo].[sp_SeparateXML_RECEIVE] AS BEGIN SET NOCOUNT ON; DECLARE @data XML; DECLARE @RecvReplyDlgHandle UNIQUEIDENTIFIER; BEGIN TRANSACTION; WAITFOR ( RECEIVE TOP(1) @RecvReplyDlgHandle = conversation_handle, @data = CAST(message_body as xml) FROM dbo.NotifyQueue_DDL ), TIMEOUT 1000; --END CONVERSATION @RecvReplyDlgHandle; IF (@data IS NOT NULL) BEGIN --邏輯處理 INSERT [SSB_DB].[dbo].[EventInfo]( [PostTime], [ServerName], [LoginName], [DatabaseUser], [DatabaseName], [Schema], [Object], [TSQL], [Event], [XmlEvent]) VALUES( @data.value('(/EVENT_INSTANCE/PostTime)[1]', 'sysname'), @data.value('(/EVENT_INSTANCE/ServerName)[1]', 'sysname'), @data.value('(/EVENT_INSTANCE/LoginName)[1]', 'sysname'), @data.value('(/EVENT_INSTANCE/UserName)[1]', 'sysname'), @data.value('(/EVENT_INSTANCE/DatabaseName)[1]', 'sysname'), @data.value('(/EVENT_INSTANCE/SchemaName)[1]', 'sysname'), @data.value('(/EVENT_INSTANCE/ObjectName)[1]', 'sysname'), @data.value('(/EVENT_INSTANCE/TSQLCommand)[1]', 'nvarchar(max)'), @data.value('(/EVENT_INSTANCE/EventType)[1]', 'sysname'), @data ); END COMMIT TRANSACTION; END GO --Step12:解析XML TRUNCATE TABLE [EventInfo] DECLARE @counts INT SELECT @counts = COUNT(1) FROM dbo.NotifyQueue_DDL WHILE(@counts > 0) BEGIN EXEC dbo.sp_SeparateXML_RECEIVE SET @counts = @counts - 1 END SELECT * FROM [EventInfo]
五.事件通知監控SQL跟蹤事件(NotifyQueue_Trace)
關於使用事件通知監控SQL跟蹤事件的文檔我基本沒有看到過,而且CSDN也沒有相關的T-SQL示例,下面就演示實現過程:
USE SSB_DB GO --Step13:創建隊列 CREATE QUEUE NotifyQueue_Trace GO --Step14:創建服務 CREATE SERVICE NotifyService_Trace ON QUEUE NotifyQueue_Trace ([http://schemas.microsoft.com/SQL/Notifications/PostEventNotification]); GO --Step15:對系統目錄視圖sys.databases進行查詢 SELECT service_broker_guid FROM sys.databases WHERE name = 'SSB_DB' /* DB19CBE8-0581-4604-B44A-812C29A565BB */ --Step16:創建用戶登陸事件通知 CREATE EVENT NOTIFICATION NotifyEvent_Trace ON SERVER FOR AUDIT_LOGIN TO SERVICE 'NotifyService_Trace', 'DB19CBE8-0581-4604-B44A-812C29A565BB'; --Step17:測試登陸,如Figure6所示 --Step18:使用Select或Recieve(其中Recieve會刪除隊列中的事件消息)查詢隊列 SELECT CAST(message_body as xml) EventInfo FROM dbo.NotifyQueue_Trace ORDER BY message_body DESC --Step19:查詢錯誤日志 EXEC xp_readerrorlog 0,1,NULL,NULL,NULL,NULL,'DESC'
Step17可以通過Figure6所示的方式進行測試,執行Step18的SQL腳本返回Figure7信息,從內容看的確是監控到了用戶登錄的信息,同樣我們可以通過Step19的SQL腳本查看錯誤日志的,效果是一樣的。
(Figure6:測試登陸)
(Figure7:SQL跟蹤事件AUDIT_LOGIN)
(Figure8:ERRORLOG讀取)
對比Figure7和Figure8的事件時間,可以看到事件先寫入了錯誤日志中,再異步寫入到事件通知的消息隊列中;
其它可以監控的SQL跟蹤事件可以通過用於事件通知的跟蹤事件進行查看,只有部分的SQL跟蹤事件可用於事件通知,注意,不是所有。
六.注意事項(Attention)
1. SERVER:將事件通知的作用域應用於 SQL Server 的當前實例。 如果已指定,則只要 FOR 子句中的指定事件在 SQL Server 的實例中發生,便會激發通知。
2. DATABASE:將事件通知的作用域應用於當前數據庫。 如果已指定,則只要 FOR 子句中的指定事件在當前數據庫中發生,便會激發通知。
3. 如果你創建了是SERVER級別的事件通知,那么在每個數據庫的[server_events] 和[server_event_notifications]視圖中都能看到相同的事件通知信息;
(Figure9:服務器級別事件通知)
4. 在獲取隊列數據的時候有兩種方式,Select或Recieve(其中Recieve會刪除隊列中的事件消息);
5. 在ON DATABASE的時候,不能使用FOR DDL_SERVER_LEVEL_EVENTS,不然會報下面的錯誤信息:
消息1098,級別15,狀態1,第2 行
指定的事件類型對指定的目標對象無效。
6. 無論是否回滾 DDL 語句,都將始終生成 DDL 生成的跟蹤事件。如果回滾相應 DDL 語句中的事件,則事件通知不會觸發。
7. 創建事件通知的時候指定解析 broker_service 所依據的 Service Broker 實例。 特定 Service Broker 的值可通過查詢 sys.databases 目錄視圖的 service_broker_guid 列來獲取。 使用 'current database' 在當前數據庫中指定 Service Broker 實例。 'current database' 是不區分大小寫的文字字符串。
8. 只能使用 Transact-SQL 語句創建事件通知。出處:CREATE EVENT NOTIFICATION
9. 創建MESSAGE TYPE和CONTRACT的時候,名稱的規范建議使用類似URL格式:[//AdventureWorks.com/Helpdesk/SupportTicket]
“The message_type_namevalue must be unique within the database and commonly uses a URL (or URL-like) convention that allows you to create a hierarchical namespace for cre-ating multiple message types for different services.”
七.疑問(Questions)
1. “SQL 跟蹤不會對與事務關聯的性能造成負面影響。打包數據很有效。創建 XML 格式的事件數據和發送事件通知會對性能造成關聯的負面影響。“這句需要怎么理解?
2. 使用CREATE EVENT NOTIFICATION創建的事件通知在SSMS哪里能找到呢?
解答:只能在動態視圖sys.event_notifications和sys.server_event_notifications看到相關的創建信息;(依據:只能使用 Transact-SQL 語句創建事件通知)
3. 觸發器必須在本地服務器上處理,事件通知可以在遠程服務器上處理?如何實現?什么場景下可以使用?
4. 事件通知的SQL跟蹤只能設置SERVER級別?不能設置DATABASE級別的?如果設置了DATABASE級別會報下面的錯誤信息:
消息1098,級別15,狀態1,第2 行
指定的事件類型對指定的目標對象無效。
解答:SQL 跟蹤事件只能運行於服務器級 (ON SERVER)(依據:用於事件通知的跟蹤事件)
5. 為什么一次登陸會造成兩條登陸信息的呢?下面的SQL語句會造成一次用戶登陸,因為需要讀取文件?
EXEC xp_readerrorlog 0,1,NULL,NULL,NULL,NULL,'DESC'
(Figure10:登陸信息)
6. 隊列是存儲在什么地方?以什么的形式存儲的?存儲怎么保證消息的安全性(數據庫宕機或者數據丟失等情況)?
解答:從下圖看來,隊列是存儲在主文件組中的。
(Figure11:隊列屬性)
八.參考文獻(References)
SQL Server 2008中新增的Service Broker事件通知
SQL Server 2008中Service Broker基礎應用(上)
SQL Server 2008中Service Broker基礎應用(下)
SQL Server 2008中遠程Service Broker實現
CREATE EVENT NOTIFICATION (Transact-SQL)(中文)