DML、DDL、LOGON觸發器html
轉自:http://www.javashuo.com/article/p-yedvcmxy-du.htmlsql
觸發器能夠理解爲由特定事件觸發的存儲過程, 和存儲過程、函數同樣,觸發器也支持CLR,目前SQL Server共支持如下幾種觸發器:數據庫
1. DML觸發器, 表/視圖級有效,可由DML語句 (INSERT, UPDATE, DELETE) 觸發;服務器
2. DDL 觸發器,數據庫級有效,可由DDL語句 (CREATE, ALTER, DROP 等) 觸發;session
3. LOGON 觸發器, 實例級有效,可由用戶帳號登陸(LOGON)數據庫實例時觸發;oracle
一. DML觸發器app
1. 語句級觸發器/行級觸發器ide
在SQL Server中,從定義來講只有語句級觸發器,但若是有行級的邏輯要處理,有兩個僅在觸發器內有效的表 (inserted, deleted), 存放着受影響的行,能夠從這兩個表裏取出特定的行並自行定義腳本處理;函數
在ORACLE中, 對錶作一次DML操做產生一次觸發,叫語句級觸發器,另外還能夠經過指定[FOR EACH ROW]子句,對於表中受影響的每行數據均觸發,叫行級觸發器,原有行用:OLD表示,新行用:NEW表示;spa
2. BEFORE/AFTER/INSTEAD OF
在SQL Server中,從定義來講只有AFTER/INSTEAD OF觸發器,在表上支持AFTER觸發器,在表/視圖上支持INSTEAD OF觸發器,對於BEFORE觸發器的需求能夠嘗試經過INSEAD OF觸發器來實現;
SQL Server DML Trigger |
BEFORE |
AFTER |
INSTEAD OF |
TABLE |
N/A |
√ |
√ |
VIEW |
N/A |
N/A |
√ |
在ORACLE中,在表上支持BEFORE/AFTER觸發器,在視圖上支持INSTEAD OF觸發器,好比ORACLE中沒法直接對視圖作DML操做,能夠經過INSTEAD OF觸發器來變樣完成;
ORACLE DML Trigger |
BEFORE |
AFTER |
INSTEAD OF |
TABLE |
√ |
√ |
N/A |
VIEW |
N/A |
N/A |
√ |
3. 觸發條件
(1) 不能觸發的狀況
對於UPDATE,DELETE操做而言,均會觸發觸發器;而對於INSERT或者說IMPORT的狀況,是能夠控制不去觸發的。
(2) 嵌套觸發器 (Nested Triggers), 循環/遞歸觸發器 (Recursive Triggers)
嵌套觸發器,就是一次操做觸發了一個觸發器,而後觸發器裏的語句繼續觸發其餘觸發器,若是繼續回頭觸發了本身,那麼就是遞歸觸發器。
對於AFTER觸發器有個兩個開關分別控制嵌套觸發和遞歸觸發:
exec sp_configure 'nested triggers'
這個參數默認值爲1, 也就是說容許AFTER觸發器嵌套,最多嵌套32層,設爲0就是不容許AFTER觸發器嵌套,以下:
exec sp_configure 'nested triggers',0 RECONFIGURE
但這個參數有兩個另外:
--create table, sql server 2016 & higher drop table if exists A GO create table A(id int) GO --create DML trigger drop trigger if exists tri_01 GO create TRIGGER tri_01 ON A AFTER INSERT, UPDATE, DELETE as begin if @@NESTLEVEL = 32 begin return end insert A values(0) end GO --check nested triggers server option exec sp_configure 'nested triggers' --name minimum maximum config_value run_value --nested triggers 0 1 1 1 --test with RECURSIVE_TRIGGERS off ALTER DATABASE dba set RECURSIVE_TRIGGERS off select is_recursive_triggers_on, * from sys.databases GO insert A values(1) select * from A --id --1 --0 --test with RECURSIVE_TRIGGERS on ALTER DATABASE dba set RECURSIVE_TRIGGERS on select is_recursive_triggers_on, * from sys.databases GO truncate table A insert A values(1) select * from A --32 rows --若是沒有加@@NESTLEVEL判斷並退出,會出現32層限制的報錯,而且表裏不會插入任何數據 /* Msg 217, Level 16, State 1, Procedure tri_01, Line 10 Maximum stored procedure, function, trigger, or view nesting level exceeded (limit 32). select * from A --0 rows */ --刪表會級聯刪除觸發器,就像索引 drop table A
循環/遞歸觸發器的前提就是嵌套觸發器,只有容許嵌套了才能夠遞歸(遞歸也就是嵌套並觸發本身),遞歸有直接和間接兩種狀況:
--create table, sql server 2016 & higher drop table if exists A drop table if exists B GO create table A(id int) create table B(id int) GO --create DML trigger drop trigger if exists tri_01 drop trigger if exists tri_02 GO create TRIGGER tri_01 ON A AFTER INSERT, UPDATE, DELETE as begin if @@NESTLEVEL = 32 begin return end insert B values(0) end GO create TRIGGER tri_02 ON B AFTER INSERT, UPDATE, DELETE as begin if @@NESTLEVEL = 32 begin return end insert A values(0) end GO --test with nested triggers server option ON exec sp_configure 'nested triggers',1 RECONFIGURE --test with RECURSIVE_TRIGGERS off ALTER DATABASE dba set RECURSIVE_TRIGGERS off select is_recursive_triggers_on, * from sys.databases GO truncate table A truncate table B insert A values(1) select * from A --16 rows select * from B --16 rows --test with RECURSIVE_TRIGGERS on ALTER DATABASE dba set RECURSIVE_TRIGGERS on select is_recursive_triggers_on, * from sys.databases GO truncate table A truncate table B insert A values(1) select * from A --16 rows select * from B --16 rows --test with nested triggers server option OFF exec sp_configure 'nested triggers',0 RECONFIGURE --test with RECURSIVE_TRIGGERS off ALTER DATABASE dba set RECURSIVE_TRIGGERS off select is_recursive_triggers_on, * from sys.databases GO truncate table A truncate table B insert A values(1) select * from A --1 select * from B --0 --test with RECURSIVE_TRIGGERS on ALTER DATABASE dba set RECURSIVE_TRIGGERS on select is_recursive_triggers_on, * from sys.databases GO truncate table A truncate table B insert A values(1) select * from A --1 select * from B --0 --刪表會級聯刪除觸發器,就像索引 drop table A, B
總結下來:
1. AFTER觸發器,默認Nest Triggers值爲1,即容許觸發器嵌套,上限32層,間接遞歸也是能夠的,直接遞歸須要開啓數據庫選項RECURSIVE_TRIGGERS;
2. INSTEAD OF觸發器,不受Nest Triggers選項影響,都可以嵌套,上限32層,間接遞歸也是能夠的,直接遞歸不管是否開啓數據庫選項RECUSIVE_TRIGGERS,都無效;把上面兩個腳本示例中的AFTER改成INSTEAD OF便可演示。
4. 觸發器中沒法commit/rollback事務
--create table, sql server 2016 & higher drop table if exists A GO create table A(id int) GO --create DML trigger drop trigger if exists tri_01 GO create TRIGGER tri_01 ON A AFTER INSERT, UPDATE, DELETE as begin if @@NESTLEVEL = 32 begin return end insert A values(0) commit end GO begin tran insert A values(1) /* Msg 3609, Level 16, State 1, Procedure tri_01, Line 10 The transaction ended in the trigger. The batch has been aborted. */
在SQL Server和Oracle中都是這樣,觸發器做爲整個事務的一部分存在,可是並不控制整個事務的提交/回滾,爲保證數據一致性,事務邏輯由觸發器外層的語句來控制。
二. DDL觸發器
SQL Server 2005開始支持DDL觸發器,它不僅限於對CREATE/ALTER/DROP操做有效,支持的DDL事件還有好比:權限的GRANT/DENY/REVOEK, 對象的RENAME, 更新統計信息等等,可經過DMV查看更多支持的事件類型以下:
select * from sys.trigger_event_types where type_name not like '%CREATE%' and type_name not like '%ALTER%' and type_name not like '%DROP%'
注意:
1. TRUNCATE不在DDL觸發器的事件類型中,SQL Server中將Truncate 歸爲DML操做語句,雖然它也並不觸發DML觸發器,就像開啓開關的大批量導入操做 (Bulk Import Operations) 同樣;
2. DDL觸發器中捕獲的信息都由EVENTDATA()函數返回,返回類型爲XML格式,須要用XQuery來讀取;
代碼示例1:記錄全部table上的某些DDL操做
--記錄全部create table操做 if OBJECT_ID('ddl_log','U') is not null drop table ddl_log GO create table ddl_log ( LogID int identity(1,1), EventType varchar(50), ObjectName varchar(256), ObjectType varchar(25), TSQLCommand varchar(max), LoginName varchar(256) ) GO if exists(select * from sys.triggers where name = 'TABLE_DDL_LOG' and parent_class_desc = 'DATABASE') drop trigger TABLE_DDL_LOG on database; GO create trigger TABLE_DDL_LOG on database for create_table as begin set nocount on declare @data xml set @data = EVENTDATA() insert into ddl_log values (@data.value('(/EVENT_INSTANCE/EventType)[1]', 'varchar(50)'), @data.value('(/EVENT_INSTANCE/ObjectName)[1]', 'varchar(256)'), @data.value('(/EVENT_INSTANCE/ObjectType)[1]', 'varchar(25)'), @data.value('(/EVENT_INSTANCE/TSQLCommand)[1]', 'varchar(max)'), @data.value('(/EVENT_INSTANCE/LoginName)[1]', 'varchar(256)') ) end GO drop table if exists test_dll_trigger; create table test_dll_trigger (id int) select * from ddl_log
代碼示例2:禁止特定角色的用戶對特定的表作DROP操做
IF exists(select * from sys.triggers where name = 'NO_DROP_TABLE' and parent_class_desc = 'DATABASE') DROP TRIGGER [NO_DROP_TABLE] ON DATABASE; GO CREATE TRIGGER NO_DROP_TABLE ON DATABASE FOR DROP_TABLE AS BEGIN DECLARE @x XML, @user_name varchar(100), @db_name varchar(100), @schema_name varchar(100), @object_name varchar(200) --select eventdata() SET @x = EVENTDATA(); SET @user_name = @x.value('(/EVENT_INSTANCE/UserName)[1]','varchar(100)'); SET @db_name = @x.value('(/EVENT_INSTANCE/DatabaseName)[1]','varchar(100)'); SET @schema_name = @x.value('(/EVENT_INSTANCE/SchemaName)[1]','varchar(100)'); SET @object_name = @x.value('(/EVENT_INSTANCE/ObjectName)[1]','varchar(100)'); --PRINT 'Current User: ' + @user_name --PRINT 'Current Database: ' + @db_name --PRINT 'Schema Name: ' + @schema_name --PRINT 'Table Name: ' + @object_name IF is_rolemember('disallow_modify_tables',@user_name) = 1 AND @db_name = 'YOUR_DB_NAME' AND @schema_name = 'YOUR_SCHEMA_NAME' AND @object_name like 'YOUR_TABLE_NAME%' BEGIN PRINT 'Dropping tables is not allowed' ROLLBACK END END GO
三. LOGON 觸發器
SQL Server 2005在SP2中悄悄引入了LOGON觸發器,做爲一個實例級的對象,它的系統視圖,定義語句和DDL/DML觸發器都是分開的。
select * from sys.server_triggers where name = 'login_history_trigger' select * from sys.server_trigger_events select OBJECT_ID('login_history_trigger') --沒法獲取
在SQL Server中,顧名思義,LOGON觸發器,只支持LOGON事件;
在ORACLE中,實例級觸發器可支持更多事件 (SERVERERROR, LOGON, LOGOFF, STARTUP, or SHUTDOWN)。
代碼示例1: 記錄全部login登陸歷史 (其實也能夠經過修改login auditing選項,來記錄成功和失敗的登陸在errorlog裏)
IF OBJECT_ID('login_history','U') is not null DROP TABLE login_history GO CREATE TABLE login_history ( FACT_ID bigint IDENTITY(1,1) primary key, LOGIN_NAME nvarchar(1024), LOGIN_TIME datetime ) GO IF EXISTS(select 1 from sys.server_triggers where name = 'login_history_trigger') DROP TRIGGER login_history_trigger ON ALL SERVER GO CREATE TRIGGER login_history_trigger ON ALL SERVER FOR LOGON AS BEGIN --IF SUSER_NAME() NOT LIKE 'NT AUTHORITY\%' AND -- SUSER_NAME() NOT LIKE 'NT SERVICE\%' IF ORIGINAL_LOGIN() NOT LIKE 'NT AUTHORITY\%' AND ORIGINAL_LOGIN() NOT LIKE 'NT SERVICE\%' BEGIN INSERT INTO DBA..login_history VALUES(ORIGINAL_LOGIN(),GETDATE()); END; END; GO --view login history after logon SELECT * FROM login_history
代碼示例2: 限制特定用戶在特定時間範圍登陸、限制鏈接數
--限制下班時間不能登陸 DROP TRIGGER IF EXISTS limit_user_login_time ON ALL SERVER GO CREATE TRIGGER limit_user_login_time ON ALL SERVER FOR LOGON AS BEGIN IF ORIGINAL_LOGIN() = 'TestUser' AND (DATEPART(HOUR, GETDATE()) < 9 OR DATEPART (HOUR, GETDATE()) > 18) BEGIN PRINT 'TestUser can only login during working hours!' ROLLBACK END END GO --限制鏈接數 DROP TRIGGER IF EXISTS limit_user_connections ON ALL SERVER GO CREATE TRIGGER limit_user_connections ON ALL SERVER WITH EXECUTE AS 'sa' FOR LOGON AS BEGIN IF ORIGINAL_LOGIN() = 'TestUser' AND (SELECT COUNT(*) FROM sys.dm_exec_sessions WHERE Is_User_Process = 1 AND Original_Login_Name = 'TestUser') > 2 BEGIN PRINT 'TestUser can only have 1 active session!' ROLLBACK END END
注意:若是LOGON觸發器把全部人都鎖在外面了怎麼辦?
Logon failed for login 'TestUser' due to trigger execution.
這時,只能經過DAC登陸SQL Server去禁用LOGON觸發器/修改邏輯以容許登陸,DAC登陸方式有遠程和本地兩種,遠程登陸須要經過sp_configure 開啓remote admin connections ,若是沒有事先開啓,那就只能選擇本地登陸方式:
服務器本地,在SSMS中經過DAC登陸
服務器本地,在cmd中經過DAC登陸
--禁用/啓用LOGON觸發器 DISABLE TRIGGER limit_user_connections ON ALL SERVER ENABLE TRIGGER limit_user_connections ON ALL SERVER
參考:
CREATE TRIGGER (Transact-SQL)
Create Nested Triggers
Transact-SQL statements
https://docs.microsoft.com/en-us/sql/t-sql/statements/statements?view=sql-server-2017
Why we can‘t use commit in trigger, can anyone give proper explanation
https://community.oracle.com/thread/1082134
Database PL/SQL Language Reference, Using Triggers
https://docs.oracle.com/cd/B28359_01/appdev.111/b28370/triggers.htm#LNPLS020