SQL Server DDL觸發器運用
一.本文所涉及的內容(Contents)
二.背景(Contexts)
說到觸發器,大家都會想到這樣的使用場景:當一個表的資料修改了,運用DML觸發插入或者更新到其它表中;那DDL觸發器(SQL Server 2005引入的新功能)會運用到什麼場景中呢?本文將為你講述4種運用DDL觸發器的場景:
1) 禁止使用者修改和刪除表;
2) 禁止使用者刪除資料庫;
3) 記錄和監控某資料庫所有的DDL操作;
4) 把DDL操作資訊以郵件的形式主動傳送通知和預警;
三.基礎知識(Rudimentary Knowledge)
DDL觸發器是由修改資料庫物件的 DDL 語句(如以 CREATE、ALTER 或 DROP)激發。
DDL觸發器支援BEFORE和AFTER事件觸發器,並在資料庫或模式級執行。通常,DDL觸發器用於監控資料庫中的重要事件。有時用它們來監控錯誤程式碼。錯誤程式碼可能會執行破壞資料庫或使資料庫不穩定的活動。更常見的情況是:在開發、測試和stage系統中用它們來了解和監控資料庫活動的動態。
當監控GRANT和REVOKE許可權語句時,它們也是有效的安全工具。
四.DDL運用場景(DDL Scene)
(一) 首先我們來看一個簡單的例子:建立資料庫DDL_DB和一個名為DatabaseLog的表,現在建立一個DDL觸發器:禁止使用者修改和刪除表,並進行提醒。執行下面的SQL指令碼進行測試。
--Script1: --建立測試資料庫 USE MASTER GO CREATE DATABASE DDL_DB --建立DDL觸發器記錄表 USE DDL_DB GO CREATE TABLE [dbo].[DatabaseLog]( [DatabaseLogID] [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_DatabaseLog_DatabaseLogID] PRIMARY KEY NONCLUSTERED ( [DatabaseLogID] ASC ) ON [PRIMARY] ) ON [PRIMARY] --Script2: --建立DDL觸發器:禁止修改或者刪除資料表 CREATE TRIGGER DDL_TableTrigger ON DATABASE FOR DROP_TABLE, ALTER_TABLE AS PRINT '對不起,您不能對資料表進行操作,請聯絡DBA' ROLLBACK ; --測試刪除表 USE DDL_DB GO DROP TABLE [DatabaseLog]
(Figure1:建立資料庫級別的DDL)
(Figure2:返回的提示資訊)
(Figure3:SSMS返回的提示資訊)
建立資料庫級別的DDL之後會出現在資料庫觸發器列表中,如Figure1;當執行刪除表的Drop等DDL命令的時候,就會出現Figure2的提示資訊;如果是在SSMS中刪除表則會出現Figure3的提示資訊。
(二) 在上面的基礎上再進行擴充套件,建立一個DDL觸發器:禁止使用者刪除資料庫,並進行提醒。
--Script3: --禁止SQL Server伺服器裡刪除資料庫 CREATE TRIGGER DDL_DataBaseTrigger ON ALL SERVER FOR DROP_DATABASE AS PRINT '對不起,您不能刪除資料庫,請聯絡DBA' ROLLBACK; --測試刪除資料庫 USE MASTER GO DROP DATABASE [DDL_DB]
(Figure4:建立伺服器級別的DDL)
(Figure5:返回的提示資訊)
(Figure6:SSMS返回的提示資訊)
建立伺服器級別的DDL之後會出現在伺服器物件-觸發器的列表中,如Figure4;當執行刪除資料庫的Drop等DDL命令的時候,就會出現Figure5的提示資訊;如果是在SSMS中刪除資料庫則會出現Figure6的提示資訊。
(三) 很多時候在程式開發階段是不會禁用對資料庫的修改的,這些時候我們更希望是記錄資料庫的修改資訊,方便對資訊進行跟蹤檢查。使用 EVENTDATA 函式,可以捕獲有關激發 DDL 觸發器的事件的資訊,此函式返回 xml 值。
前面已經建立了資料表DatabaseLog,建立下面的DDL_DatabaseLog觸發器,每當資料庫發生DDL事件,DDL觸發器就會把相關的DDL資訊插入到DatabaseLog表,資訊包括操作的時間,操作人,操作的SQL等。
執行Script5測試指令碼,返回Figure7的資訊,查詢DatabaseLog表,返回的記錄有2條,一條是建立表資訊,一條是刪除表資訊,如Figure8、Figure9所示。
--Script4: --建立當前資料庫的DDL觸發器 USE DDL_DB GO -- ============================================= -- Author: <聽風吹雨> -- Create date: <2013.05.03> -- Description: <記錄資料庫DDL操作> -- Blog: <http://www.cnblogs.com/gaizai/> -- ============================================= CREATE TRIGGER [DDL_DatabaseLog] ON DATABASE FOR DDL_DATABASE_LEVEL_EVENTS AS BEGIN SET NOCOUNT ON; DECLARE @data XML; DECLARE @schema sysname; DECLARE @object sysname; DECLARE @eventType sysname; SET @data = EVENTDATA(); SET @eventType = @data.value('(/EVENT_INSTANCE/EventType)[1]', 'sysname'); SET @schema = @data.value('(/EVENT_INSTANCE/SchemaName)[1]', 'sysname'); SET @object = @data.value('(/EVENT_INSTANCE/ObjectName)[1]', 'sysname') IF @object IS NOT NULL PRINT ' ' + @eventType + ' - ' + @schema + '.' + @object; ELSE PRINT ' ' + @eventType + ' - ' + @schema; IF @eventType IS NULL PRINT CONVERT(nvarchar(max), @data); INSERT [DDL_DB].[dbo].[DatabaseLog]( [PostTime], [ServerName], [LoginName], [DatabaseUser], [DatabaseName], [Schema], [Object], [TSQL], [Event], [XmlEvent]) VALUES( GETDATE(), @data.value('(/EVENT_INSTANCE/ServerName)[1]', 'sysname'), @data.value('(/EVENT_INSTANCE/LoginName)[1]', 'sysname'), CONVERT(sysname, CURRENT_USER), @data.value('(/EVENT_INSTANCE/DatabaseName)[1]', 'sysname'), CONVERT(sysname, @schema), CONVERT(sysname, @object), @data.value('(/EVENT_INSTANCE/TSQLCommand)[1]', 'nvarchar(max)'), @eventType, @data ); END; --Script5:測試DDL記錄 --禁用DDL 觸發器 DISABLE TRIGGER DDL_TableTrigger ON DATABASE; GO CREATE TABLE TestTable (a int) GO DROP TABLE TestTable; GO SELECT * FROM [DatabaseLog]; GO
(Figure7:返回的提示資訊)
(Figure8:DatabaseLog表前半部分資訊)
(Figure9:DatabaseLog表後半部分資訊)
(四) 我們可以使用DDL觸發器主動監控DDL語句的執行,當有對資料庫執行DDL就會觸發,我們把這些資訊儲存到表中,並且把操作使用者的HostName和修改的T-SQL以郵件的形式傳送到指定的郵件。關於設定資料庫郵件可以參考:SQL Server 資料庫郵件。傳送郵件的效果如Figure10。郵件部分參考:MS SQL監控資料庫的DDL操作
--Script5: --建立當前資料庫的DDL觸發器 USE DDL_DB GO -- ============================================= -- Author: <聽風吹雨> -- Create date: <2013.05.03> -- Description: <記錄資料庫DDL操作,傳送郵件預警> -- Blog: <http://www.cnblogs.com/gaizai/> -- ============================================= CREATE TRIGGER [DDL_DatabaseLog] ON DATABASE FOR DDL_DATABASE_LEVEL_EVENTS AS BEGIN SET NOCOUNT ON; DECLARE @data XML; DECLARE @schema sysname; DECLARE @object sysname; DECLARE @eventType sysname; DECLARE @databaseName sysname; DECLARE @tableHTML NVARCHAR(MAX); SET @data = EVENTDATA(); SET @eventType = @data.value('(/EVENT_INSTANCE/EventType)[1]', 'sysname'); SET @schema = @data.value('(/EVENT_INSTANCE/SchemaName)[1]', 'sysname'); SET @object = @data.value('(/EVENT_INSTANCE/ObjectName)[1]', 'sysname'); SET @databaseName = @data.value('(/EVENT_INSTANCE/DatabaseName)[1]', 'sysname'); IF @object IS NOT NULL PRINT ' ' + @eventType + ' - ' + @schema + '.' + @object; ELSE PRINT ' ' + @eventType + ' - ' + @schema; IF @eventType IS NULL PRINT CONVERT(nvarchar(max), @data); INSERT [DDL_DB].[dbo].[DatabaseLog]( [PostTime], [ServerName], [LoginName], [DatabaseUser], [DatabaseName], [Schema], [Object], [TSQL], [Event], [XmlEvent]) VALUES( GETDATE(), @data.value('(/EVENT_INSTANCE/ServerName)[1]', 'sysname'), @data.value('(/EVENT_INSTANCE/LoginName)[1]', 'sysname'), CONVERT(sysname, CURRENT_USER), @databaseName, CONVERT(sysname, @schema), CONVERT(sysname, @object), @data.value('(/EVENT_INSTANCE/TSQLCommand)[1]', 'nvarchar(max)'), @eventType, @data ); SET @tableHTML = N'<H1>DDL Event</H1>' + N'<table border="0">' + N'<tr><th>PostTime</th><th>ServerName</th><th>LoginName</th><th>DatabaseUser</th><th>DatabaseName</th><th>Object</th>' + N'<th>TSQL</th></tr>' + CAST((SELECT td = [PostTime],'', td = [ServerName],'', td = [LoginName],'', td = [DatabaseUser],'', td = [DatabaseName],'', td = [Object],'', td = TSQL,'' FROM [DDL_DB].[dbo].[DatabaseLog] WHERE DatabaseLogID =(SELECT MAX(DatabaseLogID) FROM [DDL_DB].[dbo].[DatabaseLog]) FOR XML PATH('tr'), TYPE) AS NVARCHAR(MAX)) + N'</table>'; DECLARE @subjectStr NVARCHAR(MAX); SET @subjectStr = 'DDL Event - DataBaseName: ' + @databaseName; EXEC msdb.dbo.sp_send_dbmail @profile_name = 'DataBase_DDL_Event', @recipients='[email protected]', @subject = @subjectStr, @body = @tableHTML, @body_format = 'HTML'; END;
(Figure10:郵件收到的預警)
五.補充說明(Addon)
(一) 關於DML、DDL、DCL、TCL的解釋:
DML
DML is abbreviation of Data Manipulation Language. It is used to retrieve, store, modify, delete, insert and update data in database.
Examples: SELECT, UPDATE, INSERT statements
DDL
DDL is abbreviation of Data Definition Language. It is used to create and modify the structure of database objects in database.
Examples: CREATE, ALTER, DROP statements
DCL
DCL is abbreviation of Data Control Language. It is used to create roles, permissions, and referential integrity as well it is used to control access to database by securing it.
Examples: GRANT, REVOKE statements
TCL
TCL is abbreviation of Transactional Control Language. It is used to manage different transactions occurring within a database.
Examples: COMMIT, ROLLBACK statements
(二) 關於DML與DDL運用場景的一些區別:
DML 觸發器可以看作是一種特殊的儲存過程,可以保證系統保持其完整性,在系統中進行級聯更新或強行業務規則。通過INSERTED 和 DELETED ,我們可以檢索哪些列被更新了。DML觸發器的本質就是當這兩個發生資料修改時自動執行的儲存過程。
DDL 觸發器的構建主要是為了安全,或者根據部門的需求對系統所進行的變更進行通報。通過使用 EVENTDATA( ) 函式,可以在觸發器中使用XML資訊。
(三) 如果是線上的系統,可以考慮做下面的限制:在工作時間,不允許修改任何儲存過程,否則回滾,示例程式碼如下:IF DATEPART(hour, GETDATE()) >=9 AND DATEPART(hour, GETDATE()) <= 17
(四) 一些維護DDL的SQL指令碼:
--啟用DDL 觸發器 ENABLE TRIGGER DDL_TableTrigger ON DATABASE; --禁用DDL 觸發器 DISABLE TRIGGER ddlDatabaseTriggerLog ON DATABASE; --刪除DDL 觸發器 DROP TRIGGER ddlDatabaseTriggerLog ON DATABASE; --禁用當前資料庫中所有資料庫級別的DDL 觸發器 DISABLE TRIGGER ALL ON DATABASE --禁用伺服器例項中所有伺服器級別的DDL 觸發器 DISABLE TRIGGER ALL ON ALL SERVER
(五) 所有的DDL事件可以檢視DDL 事件,也可以通過下面的SQL進行檢視:
--獲取有關DDL 觸發器可觸發的事件或事件組的資訊 SELECT * FROM sys.trigger_event_types --檢視觸發器的依賴關係 SELECT * FROM sys.sql_expression_dependencies SELECT * FROM sys.dm_sql_referenced_entities SELECT * FROM sys.dm_sql_referencing_entities --獲取有關資料庫範圍內的觸發器的資訊 SELECT * FROM sys.triggers --獲取有關激發觸發器的資料庫事件的資訊 SELECT * FROM sys.trigger_events SELECT * FROM sys.trigger_events AS a LEFT join sys.triggers AS b ON a.object_id=b.object_id WHERE name = 'ddlDatabaseTriggerLog' --獲取有關伺服器範圍內的觸發器的資訊 SELECT * FROM sys.server_triggers SELECT * FROM sys.server_trigger_events --檢視資料庫範圍內的觸發器的定義 SELECT * FROM sys.sql_modules
(六) 在執行Script3的時候如果你正在使用SSMS開啟這個資料庫(SPID)的話,那有可能不是出現Figure5的錯誤資訊,而是出現Figure11的錯誤,這是因為你沒有關閉SPID這些視窗,我還沒有在程式連線的情況測試是否會返回這些資訊:
(Figure11:Figure5可能出現的)
(七) 如果你想修改DDL觸發器的內容,那麼你不能直接Alter DDL,而應該是先執行Drop DDL,之後在Create DDL。
(八) 之前已經建立了DDL_TableTrigger和DDL_DatabaseLog觸發器,這兩個觸發器都是在DDL_DB資料庫中建立的,當我們需要修改DDL觸發器,應該觸發物件從小到大進行修改,即DDL_TableTrigger(表)到DDL_DatabaseLog(資料庫)進行修改。
如Figure12所示,如果只修改DDL_TableTrigger(Drop、Create),再執行下面的指令碼將會出現Figure13的錯誤(還沒找到官方理論描述)。解決辦法就是對DDL_DatabaseLog進行建立建立(Drop、Create)。
--測試刪除表 USE MASTER GO DROP DATABASE [DDL_DB]
(Figure12:DDL觸發器列表)
(Figure13:錯誤資訊)
七.疑問(Questions)
(一) 刪除DDL觸發器是否也可以觸發一個事件呢?不然如何防止使用者先刪除DDL觸發器之後再做DDL操作呢?難道是使用者許可權?
解答:第一種方法,可以對DDL觸發器進行許可權控制;第二種方式就是在伺服器級別加一個DROP的觸發器,可以監控各個資料庫的DDL觸發器;下圖Figure14是DDL_DatabaseLog被刪除時的預警;
(Figure14:刪除DDL觸發器)
(二) 能對所有資料庫進行DDL監控?一條DDL預警能實現?
解答:可以在DDL_DatabaseLog把 ON DATABASE 設定為ON All SERVER,這樣就可以監控整個伺服器例項,下圖Figure15是Logon_DB的DDL預警;
(Figure15:刪除DDL觸發器)