触发器可以理解为由特定事件触发的存储过程, 和存储过程、函数一样,触发器也支持CLR,目前SQL Server共支持以下几种触发器:

1. DML触发器, 表/视图级有效,可由DML语句 (INSERT, UPDATE, DELETE) 触发;

2. DDL 触发器,数据库级有效,可由DDL语句 (CREATE, ALTER, DROP 等) 触发;

3. LOGON 触发器, 实例级有效,可由用户账号登录(LOGON)数据库实例时触发;

. DML触发器

1. 语句级触发器/行级触发器

在SQL Server中,从定义来说只有语句级触发器,但如果有行级的逻辑要处理,有两个仅在触发器内有效的表 (inserted, deleted), 存放着受影响的行,可以从这两个表里取出特定的行并自行定义脚本处理;

在ORACLE中, 对表做一次DML操作产生一次触发,叫语句级触发器,另外还可以通过指定[FOR EACH ROW]子句,对于表中受影响的每行数据均触发,叫行级触发器,原有行用:OLD表示,新行用:NEW表示;

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的情况,是可以控制不去触发的。

  • 大批量导入操作,如:BULK INSERT, bcp/INSERT... SELECT * FROM OPENROWSET,都有FIRE_TRIGGERS/IGNORE_TRIGGERS选项,可以设置是否触发触发器;
  • 导入导出向导/SSIS,如果目标是表,也有FIRE_TRIGGERS的设置选项;
  • 另外truncate操作也不会触发;

(2) 嵌套触发器 (Nested Triggers), 循环/递归触发器 (Recursive Triggers)

嵌套触发器,就是一次操作触发了一个触发器,然后触发器里的语句继续触发其他触发器,如果继续回头触发了自己,那么就是递归触发器。

对于AFTER触发器有个两个开关分别控制嵌套触发和递归触发:

exec sp_configure 'nested triggers'

这个参数默认值为1, 也就是说允许AFTER触发器嵌套,最多嵌套32层,设为0就是不允许AFTER触发器嵌套,如下:

exec sp_configure 'nested triggers',0
RECONFIGURE

但这个参数有两个另外:

  • INSTEAD OF触发器,可以嵌套,不受这个参数开关与否影响;
  • AFTER触发器,即使打开该选项,也不会自己嵌套自己(即递归),除非打开了RECURSIVE_TRIGGERS选项,也就是循环/递归触发器;
--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
--
-- --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

循环/递归触发器的前提就是嵌套触发器,只有允许嵌套了才可以递归(递归也就是嵌套并触发自己),递归有直接和间接两种情况:

  • 直接递归:就是A表的DML触发器再回来对A表进行DML操作,如上例;
  • 间接递归:就是A表DML触发器去操作B表,然后B表上触发器回来操作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 --
select * from B -- --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 --
select * from B -- --删表会级联删除触发器,就像索引
drop table A, B
  • 可以看出数据库选项RECURSIVE_TRIGGERS,仅对直接递归有效,对间接递归无效;可以通过Nest Triggers的开关来控制是否允许嵌套,从而控制是否允许间接递归;
  • 不论直接递归,还是间接递归,递归次数都有32次嵌套的上限;

总结下来:

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)

https://docs.microsoft.com/en-us/sql/t-sql/statements/create-trigger-transact-sql?view=sql-server-2017

Create Nested Triggers

https://docs.microsoft.com/en-us/sql/relational-databases/triggers/create-nested-triggers?view=sql-server-2017

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

15. DML, DDL, LOGON 触发器的更多相关文章

  1. (4.30)全面了解触发器:DML、DDL、LOGON触发器

    DML.DDL.LOGON触发器 转自:https://www.cnblogs.com/seusoftware/p/9120632.html 触发器可以理解为由特定事件触发的存储过程, 和存储过程.函 ...

  2. SQLServer之创建LOGON触发器

    LOGON触发器工作原理 登录触发器将为响应 LOGON 事件而激发存储过程. 与 SQL Server实例建立用户会话时将引发此事件. 登录触发器将在登录的身份验证阶段完成之后且用户会话实际建立之前 ...

  3. SQLServer之创建DML AFTER UPDATE触发器

    DML AFTER UPDATE触发器创建原理 触发器触发时,系统自动在内存中创建deleted表或inserted表,inserted表临时保存了插入或更新后的记录行,deleted表临时保存了删除 ...

  4. SQLServer之创建DML AFTER INSERT触发器

    DML AFTER INSERT触发器创建原理 触发器触发时,系统自动在内存中创建deleted表或inserted表,内存中创建的表只读,不允许修改,触发器执行完成后,自动删除. insert触发器 ...

  5. SQL语句 DML,DDL,DCL

    数据控制语言(DCL)是用来设置或者更改数据库用户或角色权限的语句,这些语句包括GRANT.DENY.REVOKE等语句,在默认状态下,只有 sysadmin.dbcreator.db_owner或d ...

  6. SQLite中DML DDL DML命令的区别[转]

    总体解释: DML(data manipulation language):       它们是SELECT.UPDATE.INSERT.DELETE,就象它的名字一样,这4条命令是用来对数据库里的数 ...

  7. SQL语句 DML,DDL,DCL(转载)

    数据控制语言(DCL)是用来设置或者更改数据库用户或角色权限的语句,这些语句包括GRANT.DENY.REVOKE等语句,在默认状态下,只有 sysadmin.dbcreator.db_owner或d ...

  8. 15、SQL Server 触发器

    SQL Server 触发器 触发器是一种特殊的存储过程,只有当试图用数据操作语言DML来修改数据时才会触发,DML包含对视图和表的增.删.改. 触发器分为DML触发器和DDL触发器,其中DML触发器 ...

  9. sql语句分为三类(DML,DDL,DCL)-介绍

    本文知识来源自:<Oracle专家高级编程> 分享作者:Vashon 时间:20150415 DDL is Data Definition Language statements. Som ...

随机推荐

  1. PHP 类与对象 全解析( 一)

    目录 PHP 类与对象 全解析( 一) PHP 类与对象 全解析( 二) PHP 类与对象 全解析(三 ) 1.类与对象 对象:实际存在该类事物中每个实物的个体.$a =new User(); 实例化 ...

  2. [日常] Go语言圣经-命令行参数

    1.编译 go build hello.go 2.go get gopl.io/ch1/helloworld 命令,就会从网上获取代码,并放到对应目录中 下载的代码会放在$GOPATH/src/gop ...

  3. SpringMVC 工作原理详解

    本文Github开源项目https://github.com/Snailclimb/JavaGuide,只供自己学习总结无商业用途,如有侵权,联系删除 先来看一下什么是 MVC 模式 MVC 是一种设 ...

  4. 【转】解决IDEA新建项目名称为红色

    idea如果当前project用了版本控制器,其下面新建的所有的项目默认都是加入到版本控制里面,所以项目名称和文件都是红色的,如图: File-->Settings-->version c ...

  5. 深入理解java虚拟机---java内存区域与内存溢出异常---1内存结构

    本文来源于翁舒航的博客,点击即可跳转原文观看!!!(被转载或者拷贝走的内容可能缺失图片.视频等原文的内容) 若网站将链接屏蔽,可直接拷贝原文链接到地址栏跳转观看,原文链接:https://www.cn ...

  6. MVC 导出Execl 的总结几种方式 (三)

    第三种方式呢,就是借用第三方插件 NPOI 来实现Execl 导出 第一步:在NuGut包中下载NPOI 组件 第二步:编辑控制器代码 public FileResult ExcelNewKeyPro ...

  7. macbook usb口突然不能用 解决方法

    1.先按shift+ctrl+opt+开机键,等待10秒,着10秒是没有反应的,屏幕不会亮,系统不会跑起来.2.10秒过后松开所以的键.3.再按opt+cmd+r+p接着按开机键,这时电脑会不断重启, ...

  8. CefSharp F12打开DevTools查看console

    winform嵌入chrome浏览器,修改项目属性 生成 平台为x86 1.nuget安装cefsharp 2.实例化浏览器 private void Form1_Load(object sender ...

  9. 使用display:none和visibility:hidden隐藏的区别

    今天做毕设时遇到了一个小问题,我做了一个tab导航栏,点击一个tab页其它tab页隐藏,这时候第一想法是使用display:none来控制显示隐藏,写了之后发现使用display会有一个问题,就是第二 ...

  10. CentOS7系列--1.4CentOS7服务

    CentOS7服务管理 1. 查看服务 1.1. 查看所有运行的服务 [root@centos7 ~]# systemctl -t service UNIT LOAD ACTIVE SUB DESCR ...