SQL Server数据库

第16章 触发器
学习导读
 前面在介绍数据完整性时,提到SQL Server
2008提供了约束、默认值,以及触发器3类对象
,用于保证数据的完整性,那时笔者只介绍了约
束和默认值,本章主要介绍触发器(Trigger)。
其实,触发器就是一类特殊的存储过程。触发器
在创建后,一般是在数据库事件的驱动下自动执
行的,即当SQL Server中某一个事件发生时,
SQL Server将自动执行其相关联的触发器。
1
触发器概述
前面介绍的存储过程都是需要手工执行的,
即在SQL语句中,用EXECUTE语句执行相应
的存储过程;而触发器这类存储过程却由SQL
Server自动执行。SQL Server之所以能够自动
执行触发器,是因为触发器在创建时,与特定
的SQL Server事件相关联。
依据触发器使用的SQL Server事件的不同,
触发器可分为:登录触发器、DDL触发器、
DML触发器。
2
登录触发器
登录触发器关联的事件是事件组
SecurityAudit的事件AuditLogin,即LOGIN事
件。如果用户遗忘了事件或事件组的概念,可
以参考前面介绍SQL Server Profiler跟踪的内容
。登录触发器是一种AFTER触发器,不过,登
录触发器不存在于特定的架构之下。
3
登录触发器的执行时机和作用
登录触发器的执行时机是用户登录到SQL Server,
具体的是在登录过程的身份验证结束之后至建立会话
之前。显然,如果身份验证失败,将不激发登录触发
器,所以触发器不能检测到用户对于登录的尝试,以
及失败的次数。不过,用户可以通过SQL Server的日
志了解到这些信息。
既然登录触发器执行于这个时期,那么就可以使用
登录触发器控制用户会话的建立过程,如限制SQL
Server的用户名及其会话次数。
4
SQL登录触发器
所谓SQL登录触发器,是指用普通的SQL语
句编写的登录触发器,主要是区别使用CLR编
写的登录触发器。如果想要在登录触发器中使
用登录事件的信息,可以参看后面DDL中介绍
的EVENTDATA函数。
5
SQL登录触发器
1.创建SQL登录触发器
创建登录触发器的SQL语句为CREATE
TRIGGER。该语句支持创建SQL语句编写的登
录触发器,也支持创建基于程序集的登录触发
器:
CREATE TRIGGER trigger_name
ON ALL SERVER
[WITH [ENCRYPTION|EXECUTE AS][,…n]
FOR LOGON
AS
{sql_statement [][,…n]}
6
SQL登录触发器
组成元素的意义:
trigger_name是登录触发器的名称,不能以#或##
开头
ALL SERVER指示登录触发器的作用域是当前服
务器
WITH ENCRYPTION指示系统加密存储触发器的
文本
EXECUTE AS指示执行登录触发器的环境
sql_statement是登录触发器的核心
7
SQL登录触发器
创建一个登录触发器login_trigger,该登录触
发器的主要功能是将登录名为
PYW\Administrator的会话过程的信息记录到数
据库database_demo的表dbo.tb_logintrigger中
。
USE master
GO
CREATE TABLE database_demo.dbo.tb_logintrigger(id
INT,name NCHAR(256),logintime DATETIME)
GO
CREATE TRIGGER login_trigger
ON ALL SERVER WITH EXECUTE AS ‘PYW\Administrator’
FOR LOGON
8
SQL登录触发器
AS
BEGIN
INSERT INTO
database_demo.dbo.tb_logintrigger(id,name,logintime)
SELECT session_id,original_login_name,login_time
FROM sys.dm_EXEC_sessions
WHERE original_login_name=‘PYW\Administrator’
COMMIT
END
9
SQL登录触发器
也可以通过使用系统视图sys.server_evnets
和sys.server_triggers查看登录触发器。
SELECT
sysst.name,sysst.parent_class_desc,sysst.type_desc,sysse.ty
pe_desc,sysse.is_trigger_event,sysse.event_group_type_des
c
FROM sys.server_events AS sysse LEFT JOIN
sys.server_triggers AS sysst ON
sysse.object_id=sysst.object_id
WHERE sysse.type_desc=‘LOGON’
10
SQL登录触发器
2.使用登录触发器
在使用CREATE TRIGGER语句创建SQL登录触发器
login_trigger后,SQL Server将自动使用该登录触发
器。在下一次登录到SQL Server数据库引擎时,就可
以看到效果。我们重新打开一个Studio程序。
待登录成功后,在数据库database_demo的表
dbo.tb_logintrigger中将包含此次登录的信息:
USE database_demo
GO
SELECT id,name,logintime
FROM database_demo.dbo.tb_logintrigger
11
SQL登录触发器
3.更改登录触发器
更改触发器的语句为ALTER TRIGGER:
ALTER TRIGGER trigger_name
ON ALL SERVER
[WITH [ENCRYPTION|EXECUTE AS] [,…n]]
FOR LOGON
AS
{ sql_statement [] [,…n] }
12
SQL登录触发器
组成元素的意义:
trigger_name是登录触发器的名称。不能以#或##
开头
ALL SERVER指定登录触发器的作用域是当前服
务器
WITH ENCRYPTION指示对创建文本加密存储
EXECUTE AS是执行登录触发器的环境
sql_statement是登录触发器的核心
13
SQL登录触发器
4.删除登录触发器
DROP TRIGGER trigger_name[,…n]
ON ALL SERVER
14
登录触发器的常见错误
用户按照上面的SQL语句创建登录触发器,当然不
会产生问题,但是,如果用户自己创建登录触发器时
,需要慎重。因为创建登录触发器与创建普通的表、
视图、存储过程或函数相比,具有一定的风险。
用户编写的登录触发器很容易造成SQL Server服务
不可访问的情况发生,这是因为登录触发器会影响正
常的会话过程。如果编写的登录触发器有些地方不正
确,那么将直接导致SQLCMD和Studio等不能访问
SQL Server服务。
15
登录触发器的常见错误
但此时SQL Server服务是良好的,只是SQL Server
服务在处理与用户的会话时,执行了错误的登录触发
器。此时必须使用DAC来连接SQL Server服务来删除
、禁用或修改该登录触发器。
16
DDL 触发器
在SQL Server 2008中,CREATE、ALTER、
DROP等语句常被称为数据定义语句,或数据定义语
言(Data Definition Language,DDL)。DDL触发器
就是与DDL相关联的一种特殊的存储过程。其实,
DDL触发器是关联DLL语句对应的SQL Server事件。
这类操作被关联相应的DDL触发器后,再执行这类操
作时,SQL Server将会自动执行相关联的DDL触发器
。DDL触发器是一种AFTER触发器,不存在于特定的
架构之下,而存在于特定数据库中或整个SQL Server
实例中。
17
DDL触发器的执行时机和作用
DLL触发器的执行时机是其关联的操作结束
以后至下一个操作开始之前。所以,DDL触发
器可以帮助用户管理和控制一些与数据库对象
定义有关的操作,如管理和记录数据库对象结
构的更改。总地来说,DDL触发器主要用在以
下几方面。
处理对数据库对象结构的更改。
在数据库对象的结构发生更改后,执行处罚期内
定义的操作。
可以记录数据库对象结构的具体更改,以及相应
的SQL Server事件。
18
DLL触发器关联的事件组
DLL触发器关联的事件组依据其使用范围,
分为服务器范围内的事件组和数据库范围内的
事件组。可以通过SQL语句,查询当前SQL
Server的事件组。
SELECT syst.parent_type,syst.type_name,syst.type
FROM sys.trigger_event types AS syst
WHERE syst.type_name LIKE ‘DDL_%’
GROUP BY
syst.parent_type,syst.type,syst.type_name
ORDER BY syst.parent_type,syst.type
19
DLL触发器关联的事件
在SQL Server中,与DLL触发器关联的事件
都属于预定义的事件组。下面将给出这些事件
的详细信息,包括事件的名称、type,以及
parent type。具体的,用户可以使用如下SQL
语句查询到与DLL触发器关联的事件。
SELECT syst.parent_type,syst.type_name,syst.type
FROM sys.trigger_event_types AS syst
WHERE syst.type_name NOT LIKE ‘DDL_%’
GROUP BY
syst.parent_type,syst.type,syst.type_name
ORDER BY syst.parent_type,syst.type_name
20
EVENTDATA函数:返回事件的XML结构
在编写DDL触发器时,可能需要获得服务器或数据
库事件的信息。这时,就需要使用EVENTDATA函数
,其语法结构如下。
返回值=EVENTDATA()
其中返回值的类型为XML。对于不同事件,
EVENTDATA函数返回的XML的属性和内容也不相同
,相应事件返回的属性保存在events.xsd文件中,该
文件路径为C:\Program Files\Microsoft SQL
Server\100\Tools\Binn\schemAS\SQL
Server\2006\11\events\events.xsd。
21
EVENTDATA函数:返回事件的XML结构
可以在SQL Server 2008中通过打开文件查看
xsd文件。在SQL Server 中,EVENTDATA函
数返回事件的XML结构就是events.xsd文件中
定义的,确切地讲,是由文件中的
<xs:complexType></xs:complexType>单元定
义的。以CREATE TABLE事件为例,其在
events.xsd中对应的单元内容如下:
22
EVENTDATA函数:返回事件的XML结构
<xs:complexType name=“EVENT_INSTANCE_CREATE_TABLE”>
<xs:sequence>
<xs:element name=“EventType” type=“SSWNAMEType”/>
<xs:element name=“PostTime” type=“xs:string”/>
<xs:element name=“SPID” type=“xs:INT”/>
<xs:element name=“ServerName” type=“PathType”/>
<xs:element name=“LoginName” type=“SSWNAMEType”/>
<xs:element name=“UserName” type=“SSWNAMEType”/>
<xs:element name=“DatabaseName” type=“SSWNAMEType”/>
<xs:element name=“SchemaName” type=“SSWNAMEType”/>
<xs:element name=“ObjectName” type=“SSWNAMEType”/>
<xs:element name=“ObjectType” type=“SSWNAMEType”/>
<xs:element name=“TSQLCommand”
type=“EventTag_TSQLCommand”/>
</xs:sequence>
</xs:complexType>
23
EVENTDATA函数:返回事件的XML结构
EVENTDATA函数返回的XML内容如下:
<EVENT_INSTANCE>
<EventType>CREATE_TABLE</EventType>
<PostTime>2008-07-09T 10:03:26.560</PostTime>
<SPID>54</SPID>
<ServerName>PYW\MSSQL2008</ServerName>
<LoginName>PYW\MSSQL2008</LoginName>
<UserName>dbo</UserName>
<DatabaseName>database_demo</DatabaseName>
<SchemaName>dbo</SchemaName>
<ObjectName>tb_CRL_DDL_trigger_test</ObjectName>
<ObjectType>TABLE</ObjectType>
<TSQLCommand>
<SetOptions ANSI_NULLS=“ON” ANSI_NULL_DEFAULT=“ON”
ANSI_PADDING=“ON” QUOTED_IDENTIFIER=“ON”
ENCRYPTED=“FALSE”/>
24
EVENTDATA函数:返回事件的XML结构
<CommandText>CREATE TABLE [dbo].[tb_CRL_DDL_trigger_test](
[id][INT] NULL,
[name] [NCHAR](10) NULL,
[in_date] [datetime] NULL
) ON [PRIMARY]
</CommandText>
</TSQLCommand>
</EVENT_INSTANCE>
这里主要使用XML的value()方法,从该XML中获取相应的值。由于
value()函数是从XML结构中获取信息的值,所以必须确保名称的大小写正
确,即在XML中是区分大小写的,否则将提取不到任何信息。
假设创建一个DDL触发器是使用CREATE_TABLE事件,那么
EVENTDATA函数返回的XML变量@data_xml可以通过value方法提取返回
的信息。
XML_value(‘(/EVENT_INSTANCE/UserName)[1]’,’NVARCHAR(2000)’)
25
SQL DDL触发器
所谓SQL DDL触发器,是指用普通的SQL语句编写
的DDL触发器,主要是区别于使用CLR编写的DDL触
发器。
1.创建SQL DDL触发器
CREATE TRIGGER语句支持SQL语句编写的DDL触
发器,也支持CLR触发器。
CREATE TRIGGER trigger_name
ON ALL SERVER|DATABASE
[WITH [ENCRYPTION|EXECUTE AS][,…n]]
FOR {event_type|event_group} [,…n]
AS
{sql_statement [][,…n]}
26
SQL DDL触发器
组成元素的意义:
trigger_name是DDL触发器的名称,不能以#和##开头
ALL SERVER是将DDL触发器的作用域指定为当前服务器
范围内,而DATABASE是指示DDL触发器保存于数据库范围
内
WITH ENCRYPTION指示将触发器文本加密存储
EXECUTE AS指定DDL触发器的执行环境
event_type是SQL Server事件的名称
event_group是SQL Server中预定义事件组的名称。该事
件组中包含一些特定的SQL Server事件
sql_statement是DDL触发器的核心
27
SQL DDL触发器
创建一个DDL触发器ddl_trigger。它将使用
EVENTDATA函数获得该触发器关联的事件信息,并
将其存储到数据库database_demo的表
dbo.ddl_trigger中。
USE database_demo
GO
CREATE TABLE tb_ddl_trigger
(id INT identity(1,1),name NCHAR(2000),inTime datetime)
GO
CREATE TRIGGER ddl_trigger
ON DATABASE
FOR CREATE_TABLE
28
SQL DDL触发器
AS
BEGIN
DECLARE @data_xml XML
SET @data_xml=EVENTDATA()
INSERT INTO
database_demo.dbo.tb_ddl_trigger(name,inTime)
VALUES(@data_xml.value(‘(/EVENT_INSTANCE/UserName)
[1]’,’NVARCHAR(2000)’),GETDATE())
END
由于创建触发器时使用了ON DATABASE,所以创建的DDL触
发器ddl_trigger被保存到了当前数据库中。如果使用ON ALL
SERVER那么创建的DDL触发器将存储在服务器对象中。
29
SQL DDL触发器
2.使用DDL触发器
在创建DDL触发器后,SQL Server将自动使用
该触发器,再执行DDL操作,系统将根据事件
查询到相应的DDL触发器,并予以执行。
USE database_demo
GO
CREATE TABLE dbo.Table_1(
id INT NOT NULL,hao NCHAR(10) NOT NULL)
GO
SELECT * FROM tb_ddl_trigger
30
SQL DDL触发器
3.更改DDL触发器
更改触发器也是用ALTER TRIGGER语句。
4.删除DDL触发器
删除触发器也是用DROP TRIGGER语句
31
SQL DDL触发器
5.查看DDL触发器的信息
SELECT syst.name AS ‘名称’,syst.parent_class_desc AS ‘作
用域’,syst.type_desc AS ‘种类’,syste.type_desc AS ‘事件
’,syssm.definition AS ‘定义’
FROM sys.triggers AS syst LEFT JOIN sys.trigger_events AS
syste ON syst.object_id=syste.object_id LEFT JOIN
sys.sql_modules AS syssm ON
syst.object_id=syssm.object_id
32
DML触发器
在SQL Server 2008中,INSERT、UPDATE
、DELETE等语句常被称为数据操纵语句,或
数据操纵语言(Data Manipulation Language
,DML)。DML触发器是与DML相关联的一种
特殊的存储过程。当DML操作被关联上相应的
DML触发器后,再执行这类操作时,SQL
Server将会自动执行相关联的DML触发器。显
然,DML触发器属于特定的数据库,是对数据
库中表或视图操作的限制和拓展。
33
DML触发器类型
DML触发器有两种分类方法:一类是依据
DML触发器关联的DML语句,将DML触发器分
为INSERT触发器、UPDATE触发器和DELETE
触发器;另一类是依据DML执行过程,将DML
触发器分为AFTER触发器和INSTEAD OF触发
器。对于一个DML操作来说,其执行过程如下
。
(1)SQL Server将创建inserted表(INSERT
和UPDATE语句)或deleted表(DELETE语句
)
(2)SQL Server执行触发操作。
34
DML触发器类型
(3)SQL Server执行INSTEAD OF触发器。该触发器直接位
于触发操作之后,用于完成一些与触发操作类似的功能,因此
称为“替代”触发器。其实,INSTEAD OF触发器的出现是为了
完善和拓展触发器的部分功能。不过,最好不要在不熟悉的情
况下使用INSTEAD OF触发器。
(4)SQL Server处理约束。这些约束就是前面数据完整性里
介绍的内容。如果INSERT、UPDATE或DELETE等语句不能
通过约束的限制,那么SQL Server将撤销前面执行INSTEAD
OF触发器的操作,并且就此退出此次DML操作,而不再执行下
面的AFTER触发器。
(5)SQL Server执行AFTER触发器。
35
DML触发器的作用
与DDL触发器相比,DML触发器的用途更为
广泛。这是因为SQL Server中DML语句要比
DDL多。所以,对于用户来说,掌握DML触发
器很重要。
DML触发器可以检查并避免错误的INSERT、
UPDATE、DELETE等DML操作,而且可以编写比
CHECK约束更为复杂的检验和限制。除此之外,
DML触发器还具有CHECK约束没有的特性,如DML
触发器可以引用其他表中的列。
36
DML触发器的作用
DML触发器可以比较数据修改前后的表,并根据
两者的差异采取相应的操作,主要是使用inseted表
或deleted表
一个表中的多个同类DML触发器允许定义多个不
同的操作来响应同一个修改语句
INSTEAD OF触发器还可以更新视图中两个基表
的记录。这一点在普通视图中是不可能的。
37
DML触发器的创建语句
在SQL Server中,创建DML触发器的语句也
是CREATE TRIGGER。不过,语法结构与前
面创建DDL触发器有所不同。创建DML触发器
的CREATE TRIGGER语句的语法结构如下。
CREATE TRIGGER [ schema_name . ]trigger_name
ON TABLE | VIEW
[ WITH [ ENCRYPTION ] [ EXECUTE AS clause ] [ ,...n ] ]
FOR | AFTER | INSTEAD OF
[ INSERT ] [ , ] [ UPDATE ] [ , ] [ DELETE ]
AS
sql_statement [ ] [ ,...n ]
38
DML触发器的创建语句
组成元素的意义:
schema_name.trigger_name是DML触发器的名
称
TABLE|VIEW是DML触发器操作的对象名称
WITH ENCRYPTION是对创建文本加密
EXECUTE AS是指定执行环境
FOR|AFTER|INSTEAD OF指定DML触发器的类
型,FOR和AFTER的含义相同。不能对视图视图定
义AFTER触发器。对于表或视图,每个DML语句最
多可以定义一个INSTEAD OF触发器
[INSERT][,][UPDATE][,][DELETE]是指定DML触
发器的类型
39
DML触发器的创建语句
sql_statement是DML触发器的核心,包含用于触
发DML触发器时相应的操作。有一些语句不允许或
不推荐在DML触发器中使用:
ALTER DATABASE
LOAD DATABASE
LOAD LOG
ALTER INDEX
DBCC DBREINDEX
CREATE DATABASE
RESTORE DATABASE
RESTORE LOG
CREATE INDEX
ALTER PARTITION FUNCTION
40
AFTER触发器
前面提到,AFTER触发器是DML操作触发器
的一类触发器。其实,如果对AFTER触发器进
行细分,那么AFTER触发器可以分为AFTERINSERT触发器、AFTER-UPDATE触发器以及
AFTER-DELETE触发器3类。
41
AFTER-INSERT触发器
所谓AFTER-INSERT触发器,是指该
INSERT触发器执行位于DML操作中AFTER触
发器的位置;而INSERT触发器是指当向表中
插入新记录数据时,SQL Server自动执行特殊
的存储过程。
42
AFTER-INSERT触发器
1.创建AFTER-INSERT触发器
在database_demo中创建一个表tb_1并在其上创建一
个AFTER-INSERT触发器。
USE database_demo
GO
CREATE TABLE tb_1(id INT,name NCHAR(10))
GO
CREATE TRIGGER dbo.insert_dml_trigger
ON tb_1
AFTER INSERT
AS
BEGIN
SELECT id,name,GETDATE() AS ‘时间’
FROM inserted
END
43
AFTER-INSERT触发器
2.查看AFTER-INSERT触发器
USE database_demo
GO
SELECT syst.name AS ‘DML触发器’,syso.name AS ‘
所在的表’
FROM sys.triggers AS syst INNER JOIN sys.objects
AS syso ON syst.parent_id=syso.object_id
当然也可以从Studio查看
44
AFTER-INSERT触发器
3.执行AFTER-INSERT触发器
向tb_1中插入一条记录时,会触发AFTERINSERT触发器。
USE database_demo
GO
INSERT INTO tb_1 VALUES(11,’LXP’)
45
AFTER-UPDATE触发器
所谓AFTER-UPDATE触发器,是指该
UPDATE触发器执行位于DML操作中AFTER触
发器的位置;而UPDATE触发器是指当更新表
中记录数据时,SQL Server自动执行特殊的存
储过程。
46
AFTER-UPDATE触发器
1.创建AFTER-UPDATE触发器
USE database_demo
GO
CREATE TABLE tb_2(id INT,name NCHAR(10))
GO
INSERT INTO tb_2 VALUES(11,’LXP’)
GO
CREATE TRIGGER dbo.update_dml_trigger
ON tb_2
AFTER UPDATE
AS
BEGIN
SELECT id,name,GETDATE() AS ‘时间’
FROM inserted
END
47
AFTER-UPDATE触发器
2.查看AFTER-UPDATE触发器
USE database_demo
GO
SELECT syst.name AS ‘DML触发器’,syso.name AS ‘
所在的表’
FROM sys.triggers AS syst INNER JOIN sys.objects
AS syso
ON syst.parent_id=syso.object_id
48
AFTER-UPDATE触发器
3.执行AFTER-UPDATE触发器
USE database_demo
GO
UPDATE tb_2 SET name=‘LIN’
WHERE id=11
49
AFTER-DELETE触发器
所谓AFTER-DELETE触发器,是指该
DELETE触发器执行位于DML操作中AFTER触
发器的位置;而DELETE触发器是指当更新表
中记录数据时,SQL Server自动执行特殊的存
储过程。
50
AFTER-DELETE触发器
1.创建AFTER-DELETE触发器
USE database_demo
GO
CREATE TABLE tb_3(id INT,name NCHAR(10))
GO
CREATE TRIGGER dbo.delete_dml_trigger
ON tb_3
AFTER DELETE
AS
BEGIN
SELECT id,name,GETDATE() AS ‘时间’
FROM deleted
END
51
AFTER-DELETE触发器
2.查看AFTER-DELETE触发器
USE database_demo
GO
SELECT syst.name AS ‘DML触发器
’,syso.name AS ‘所在的表’
FROM sys.triggers AS syst INNER JOIN
sys.objects AS syso
ON syst.parent_id=syso.object_id
52
AFTER-DELETE触发器
3.执行AFTER-DELETE触发器
USE database_demo
GO
DELETE FROM tb_3 WHERE id=11
53
INSTEAD OF触发器
前面已经介绍了创建AFTER触发器,不过,
AFTER触发器仅支持表。本节介绍的INSTEAD
OF触发器也包括AFTER-INSERT触发器、
AFTER-UPDATE触发器和AFTER-DELETE触
发器,但它除支持表外,还支持视图。由于前
面已经详细介绍了AFTER触发器的创建、查看
以及使用方法,因此这里不再赘述。本节将通
过示例来介绍为视图创建INSTEAD OF触发器
。
54
INSTEAD OF触发器
创建两个基表tb_4和tb_5,基于这两个表创
建视图v_1,然后为v_1创建一个INSTEAD OF
类型的INSERT 触发器。
USE database_demo
GO
CREATE TABLE tb_4(id INT,name NCHAR(10))
GO
CREATE TABLE tb_5(id INT,salary INT)
GO
CREATE VIEW v_1(id,name,salary)
AS
55
INSTEAD OF触发器
SELECT tb_4.id,tb_4.name,tb_5.salary
FROM tb_4 INNER JOIN tb_5 ON tb_4.id=tb_5.id
GO
CREATE TRIGGER
dbo.insert_INSTEADOF_dml_trigger
ON v_1
INSTEAD OF INSERT
AS
BEGIN
INSERT INTO tb_4
SELECT id,name FROM inserted
56
INSTEAD OF触发器
INSERT INTO tb_5
SELECT id,salary FROM inserted
SELECT * FROM tb_4
SELECT * FROM tb_5
END
GO
57
INSTEAD OF触发器
向v_1的两个基表同时插入记录,触发器会返
回新插入的记录信息。
USE database_demo
GO
INSERT INTO v_1 VALUES(1,’LIN’,1200)
58
小结
 本章主要介绍触发器的使用。触发器是数据
约束很重要的一个技术手段,在更新某个数
据时还要修改谁、修改什么,主要用触发器
来完成。由于在系统优先级方面,触发器高
于存储过程,所以在对重要数据更新时,一
般都使用触发器,但滥用触发器也会降低系
统速度。触发器是双刃剑,在使用时,一定
要谨慎。
59