22 触发器
这篇文章主要讨论了触发器,触发器是存在在PL/SQL或者JAVA中的存储过程,只要当一张表或者视图被修改或者当一些用户或者数据库系统的活动发生时就会运行触发器。
这篇文章主要讨论了下面的内容:
*触发器的介绍
*触发器的组成部分
*触发器的类型
*触发器的执行
触发器的介绍
你可以下面有一个操作发生时你可以触发触发器:
1、 任何用户对一个特殊的表或者视图执行的DML语句(INSERT,UPDATE,DELETE);
2、 被数据库中的一个特殊的方案/用户或者任何方案/用户执行的DDL语句;
3、 也可以是数据库中的一个特殊的方案/用户或者任何方案/用户执行的被数据库事件,比如:登陆/退出,错误,或者启动/关闭操作;
触发器类似于存储过程。一个存储在数据库中的触发器能够包括SQL以及PL/SQL或者Java语句并且将这些作为一个程序单元来运行,触发器也可以调用存储过程。但是,存储过程和触发器之间的不同是调用的方式不一样。一个存储过程可以被一个用户,应用程序或者一个触发器显式地调用运行。当触发事件被发生时,触发器被oracle被隐式地触发,触发器的运行和连接到数据库中的用户或者和使用的应用程序是无关的。
图22-1 显示了使用一些SQL语句的数据库应用程序隐式地触发存储在数据库中的一些触发器。注意数据库存储触发器是与其相关的表分别存储的。
图22-1 触发器
一个触发器也能够调用外部的C存储过程,这样的方式对于计算密集型操作是有用的。
触发一个触发器的事件包括:
*对一张表中的数据修改所做的DML操作(INSERT,UPDATE,或者DELETE);
*DDL语句
*系统事件,比如启动,关闭,以及错误消息
*用户事件,比如:登陆以及退出
注意:oracle Forms也可以定义,存储以及运行不同种类的触发器。但是不要将oracle Forms触发器和这篇文章所讨论的触发器相混淆。
触发器是如何被使用的
Oracle触发器所提供的标准功能能够提供高度用户自定义的数据库管理系统。比如,在正常的业务时间内一个触发器可以限制对一些表的DML操作。你也可以使用触发器完成以下任务:
*自动地产生派生列值;
*防止无效的事务破坏数据
*强制实现复杂的安全授权
*在分布式系统中强制实现引用完整性约束
*强制实现复杂的业务原则
*实现透明事件日志功能
*实现审计
*实现同步的表复制
*当对视图执行DML语句时修改相关的表数据
*为订阅应用程序发布有关数据库事件,用户事件以及SQL语句。
使用触发器的一些注意点
尽管触发器对与自定义一个数据库是有用的,当在必要的时候使用触发器是有用的的。过多的使用触发器导致复杂的相互依赖,在大的应用程序中这些复杂的相互依赖关系对于维护来说是非常困难的。比如:当一个触发器被触发时,在触发器的活动中的一个SQL语句可能会潜在地触发其他触发器,导致连锁触发。这有可能会造成不可预料的效果。图22-2 说明了连锁触发。
图22-2 连锁触发
使用触发器和声明完整性约束的比较
你可以使用触发器和完整性约束来定义和强制实现任何类型的完整性原则。但是,oracle强烈推荐只有下面的情况下你可以使用触发器来约束数据的输入:
*当子表和父表在分布式数据库中的不同节点中的时候,需要强制实现引用完整性约束;
*需要强制实现无法通过使用完整性约束定义的复杂的业务原则;
*当使用完整性约束一个需要引用完整性约束原则不能够被强制实现时:
*NOT NULL,UNIQUE
*PRIMARY KEY
*FOREOGN KEY
*CHECK
*DELETE CASCADE
*DELETE SET NULL
触发器的组成部分
一个触发器由三个基本部分组成:
*触发事件或者触发语句
*触发限制
*触发活动
图22-3 展现了一个触发器中的每个部分并且没有精确的显示语法。紧接着的这部分将更加详细地解释一个触发器中的每个部分:
图22-3 触发器REORDER
触发事件和触发语句
一个触发事件和触发语句是一个能够触发器触发的SQL语句。一个触发事件可以是下面的一个或者多个:
*对于一张表做INSERT,UPDATE,或者DELETE操作的语句;
*对任何方案对象做CREATE,ALTER,或者DROP语句;
*数据库启动或者实例关闭
*一个特定的错误信息或者任何错误的信息
*用户登陆或者退出
比如:图22-3 触发语句是:
…….. update of parts_on_hand ON inventory…..
该语句就是表示当表inventory表中行记录中的parts_on_hand列上的值被更新时。当触发事件是一个UPDATE语句,你可以在列表中鉴别出哪列必须被更新来触发该触发器。你不可以为INSERT 和DELETE语句指定一个列表,因为INSERT 和DELETE语句会影响整个行的信息。
一个触发器能够指定多个SQL语句:
….INSERT OR UPDATE OR DELETE OF inventory ….
该语句意味着当对inventory表声明INSERT,UPDATE或者DELETE语句时,触发触发器。当多种SQL语句类型能够触发一个触发器时,你可以使用条件谓词来检测触发语句的类型。按照这种方式,你可以创建一个单独的触发器,该触发器可以根据触发触发器的语句的类型来运行不同的代码。
触发限制
一个触发限制指定了一个布尔表达式,该表达式如果是TRUE时可以触发触发器。如果触发限制的表达式的结果是FALSE 或者是UNKNOWN的话,触发活动是不能运行的。在这个例子中,触发限制是:
New.parts_on_hand < new.reorder_point
因此,触发器不会触发除非零件库存数量小于重下订单需求数量。
触发活动
一个触发活动是一个包含SQL语句和代码的存储过程(PL/SQL块,Java程序,或者C外调程序),只有在下面的事件发生时才运行触发活动:
*一个触发语句被声明
*触发器限制条件的结果是TRUE
和存储过程类似,一个触发活动可以是:
*包含SQL,PL/SQL,或者Java语句
*定义PL/SQL与语言结构比如:变量,常量,游标,异常。
*定义Java语言结构
*调用存储过程
如果触发器是行触发器,在触发活动中的语句能够访问被触发器正在处理的行的所有列值。相关的命名规则可以提供访问每列的旧值和新值。
触发器的类型
这部分描述了不同类型的触发器:
*行触发和语句触发
*BEFORE和AFTER触发器
*基于系统事件和用户事件的触发
行触发和语句触发
当你定义一个触发器时,你可以指定触发操作执行的次数:
*被触发语句所影响的每行数据都会执行一次触发操作,比如:一个会更新许多行的UPDATE语句触发的触发器将会触发多行。
*触发语句执行一次就触发一次触发器,与多少行记录受影响无关。
行触发器
每次表被触发语句影响的话就会有行触发器被触发。比如:如果一个UPDATE语句更新了表中的多行记录,对于该UPDATE语句所影响的所有行都会触发一个行触发器。
如果在触发器操作中的代码依赖触发语句或者受影响的行所提供的数据的话,行触发器是非常有用的。比如:图22-3说明行触发器,需要依据触发语句所影响的行的数据来执行。
语句触发器
一旦触发语句被执行时,语句触发器就被触发,与触发语句影响表中的行的数量无关,甚至如果没有数据行受影响也要触发语句触发器。比如:如果 一个DELETE语句删除了表中的一些行数据,一个语句触发器只被触发一次。
如果在触发操作中的代码不依赖于触发语句所提供的数据或者不依赖受影响的行,那么语句触发器是非常有用的。比如:使用语句触发器来:
*对于当前的时间和用户进行实时的检查;
*产生一个单独的审计记录
BEFORE和AFTER触发器
当定义一个触发器,你可以社顶触发实际—是否触发操作在触发语句运行之前还是在运行之后运行。BEFORE和AFTER适合于语句触发器,也适合于行触发器。
被DML语句触发的BEFORE和AFTER触发器只能对表定义,而不能对视图定义BEFORE和AFTER触发器。但是,如果INSERT,UPDATE或者DELETE语句是针对视图进行的,那么基于基表的触发器也会被触发。被DDL语句触发的BEFORE和AFTER触发器只能够定义在数据库上或者方案上,而不是定义在特殊的表上。
BEFORE触发器
BEFORE触发器在触发语句被运行之前就运行触发操作。这种类型的触发器只使用在下面的情形下:
*触发操作决定触发语句是否应该可以被执行。此时,为了这个目的使用BEFORE触发器,你可以在触发操作中出现异常时消除触发语句引起不必要的处理,并且最终回滚触发语句。
*可以在完成INSERT或者UPDATE触发语句之前,计算特定的列值。
AFTER触发器
AFTER触发器在运行触发语句之后运行触发操作。
触发类型结合
使用先前所列出的选项,你可以创建四种类型的行触发器和语句触发器。
*BEFORE语句触发器
在执行触发语句之前,触发操作可以运行
*BEFORE行触发器
在修改触发语句所影响的每行之前以及在检查适当的完整性约束之前,如果触发限制没有违反的话,触发操作就会被运行。
*AFTER语句触发
在执行触发语句之后以及应用任何延迟的完整性约束之后,触发操作被运行
*AFTER行触发器
在修改被触发语句影响的每行记录之后或者在应用适当的完整性约束之后,假设触发限制没有违反的话触发操作将被运行。不象BEFORE行触发器,AFTER行触发器会锁住数据行。
你可以为任何给定的表创建多个相同语句相同类型的触发器。比如:对于employees表你可以为UPDATE语句定义两个BEFORE语句触发器。这种特性允许不同的应用程序对相同表创建多个相同类型的触发器。Oracle的物化视图日志也使用AFTER行触发器,所以除了oracle定义的AFTER触发器之外你可以设计定义属于你自己的AFTER行触发器。
你可以根据你的需要为每个类型的DML语句创建许多前述的不同类型的触发器,(INSERT,UPDATE,或者DELETE)。
INSTEAD OF 触发器
INSTEAD OF 触发器提供了一个透明的修改视图的方法,这种方法不能够通过DML语句直接修改。这些触发器被称为INSTEAD OF触发器,因为,它不象其他类型的触发器,INSTEAD OF触发器只执行触发操作不执行触发语句。
你可以对视图编写正常的INSERT,UPDATE,以及DELETE语句并且适当使用INSTEAD OF触发器来更新基表中的记录。对于被修改视图的每一行INSTEAD OF触发器都会触发一次。
修改视图数据
修改视图数据对基表的影响是不确定的:
*删除在视图中的数据行或者意味着从基表中删除该数据行,或者更新一些值以便该行数据不再被视图所选择
*向视图中插入数据行意味着或者是向基表中插入数据或者更新一条存在的数据行以便在视图中能够被查询到
*更新一个包含连表的视图中的一列可能会修改没有出现在视图定义中的其他列
对象视图呈现额外的问题。比如:一个对象视图的主要作用是呈现主信息和明细信息的关系。这个操作不可避免的包括连表,修改此类视图对基表的影响是不确定的。
由于这些不确定性,对哪些视图被修改会有许多限制。对于不可修改的关系型数据库以及对象视图可以使用INSTEAD OF触发器。
如果一个视图的数据可以不需要使用INSTEAD OF触发器就可以直接进行插入,更新,或者删除操作的话,以及如果该视图遵循下面列出的限制时,该视图被称为内在可修改的视图。尽管该视图是内在可修改的,你可能想对被插入的,更新,或者被删除的数据值执行确认。INSTEAD OF可以在这个情况下使用,在这种情况下触发器代码对被修改的数据行执行确认验证,并且如果是验证是有效的,才会对基表进行修改。
INSTEAD OF触发器也能够使你通过OCI来修改在客户端上的对象视图实例。如果视图是不是内在可修改的,那么你必须定义INSTEAD OF触发器来修改在客户端对象缓存中的对象视图以及将该视图刷新到持久存储中。但是,对于只需锁定以及读取在对象缓存中的对象视图,则无须定义这些触发器。
不可修改数据的视图
如果视图查询包含下面的任何结构,视图就不是内在可修改视图,并且你因此不能够对视图执行插入,更新或者删除操作:
*集合操作符
*聚集函数
*group by,connect by或者start with语句
*distinct操作符
*连表(但是,一些连表视图是可更新的)
如果视图包含一些虚列或者表达式,你只能够使用没有引用虚列或者表达式的UPDATE语句来更新视图。
嵌套表上的INSTEAD OF触发器
你不能够使用TABLE语句来直接修改视图中改嵌套表列上的元素。但是,你可以通过对视图中的嵌套表列定义INSTEAD OF触发器拉修改嵌套表列上的元素。如果嵌套表元素被更新,插入,或者删除的话,在嵌套表上的触发器就会触发,并且对基表进行实际的修改。
基于系统事件和基于用户事件的触发器
你可以使用触发器来向订阅者发布关于数据库事件的信息。应用程序能够订阅数据库事件,这与订阅其他应用程序发布的消息类似。这些数据库事件包括:
*系统事件
*数据库启动和关闭
*Data Guard角色变换
*服务错误消息事件
*用户事件
*用户登陆和退出
*DDL语句(CREATE,ALTER,以及DROP)
*DML语句(INSERT,DELETE,以及UPDATE)
基于系统事件的触发器可以定义在数据级别或者方案级别。DBMS_AQ包是使用数据库触发器来执行一定操作的冽子。比如:一个数据库关闭触发器被定义在数据库级别:
Create trigger register_shutdown
ON DATABASE
SHUTDOWN
BEGIN
….
DBMS_AQ.ENQUEUE(….);
….
END;
在DDL语句上或者基于用户登陆或者退出事件的触发器也能够被定义在数据库级别或者方案级别。基于DML语句的触发器能够定义在表和视图上。一个定义在数据库级别的触发器可以被所有的用户触发,并且定义在方案级别或者表级别上的触发器只有当触发事件包含在方案或者表中的时才被触发。
事件发布
事件发布采用的是oracle的数据流高级队列的发布--订阅机制。队列作为一个信息存储库,存储了各种订阅者感兴趣的信息。当指定的系统或者用户事件发生时,触发器使用dbms_qa包向队列中添加一个信息。
事件属性
每个事件允许在触发器的代码中使用属性。比如:数据库启动和关闭触发器有实例号以及数据库名的属性信息,还有登陆和退出系统触发器也有用户名的属性信息。当事件发生时如果你想发布该事件的属性的话,你可以在当创建触发器时定义与属性名同名的函数名。当触发器被触发时,属性的值然后被传给相应的函数。对于在DML语句上的触发器,:OLD列上的值能够传给:NEW列值。
系统事件
那些能够触发触发器的系统事件主要包括数据库实例启动和关闭以及错误消息。基于数据库启动和关闭所创建的触发器只能定义在数据库级别,而基于错误消息的触发器可以定义在数据库级别或者在方案级别。
*当数据库被一个实例打开时,STARTUP触发器被触发。STARTUP触发器的属性包括系统事件,实例号以及数据库名。
*在服务开始关闭数据库实例之前,SHUTDOWN触发器被触发。当数据库被关闭时,你可以使用这些SHUTDOWN触发器来使订阅了数据库关闭事件的应用程序也同时完全关闭。对于不正常实例关闭,这些SHUTDOWN触发器不能够被触发。SHUTDOWN触发器的属性包括系统事件,实例号,以及数据库名;
*当一个指定的错误发生或者如果没有错误被指定的话任何一个错误发生时,SERVERERROR触发器就会被触发。SERVERERROR触发器的属性包括系统事件,以及错误号;
*当一个角色转换(故障切换或者一般切换)发生在Data Duard配置中时,DB_ROLE_CHANGE触发器将被触发。当角色转换发生时,触发器通知用户,以便客户端连接能够转移到新的主数据库上继续进行处理并且应用程序能够继续运行。
用户事件
用户事件能够触发那些与用户登陆和退出,DDL语句以及DML语句相关的触发器。
基于LOGON和LOGOFF事件的触发器
基于LOGON和LOGOFF事件的触发器可以是数据库级别的或者是方案级别的。基于LOGON和LOGOFF事件的触发器的属性包括系统事件以及用户名,并且此类触发器可以使用USERID和USERNAME来定义简单的条件。
*LOGON触发器在一个用户成功地登陆之后被触发
*LOGOFF触发器在一个用户退出之前被触发
基于DDL语句的触发器
DDL触发器可以是与数据库级别的或者是方案级别的。DDL触发器包括系统事件,方案对象的类型,以及方案对象的名字。此类触发器可以使用方案对象的名字和类型,或者USERID以及UNSERNAME的函数来定义简单的条件。DDL触发器包括的触发器的类型如下:
*当一个方案对象在数据库或者在方案中被创建时,BEFORE CREATE以及AFTER CREATE触发器将被触发;
*当一个方案对象在数据库或者在方案中被修改时,BEFORE ALTER以及AFTER ALTER触发器将被触发;
*当一个方案对象在数据库或者在方案中被删除时,BEFORE DROP和AFTER DROP触发器被触发;
基于DML语句的触发器
针对事件发布的DML触发器是与数据库表相关的。对于指定的DML操作发生的每行上,此类触发器出发的触发器或者是BEFORE触发器或者AFTER触发器。Oracle无法采用定义在视图上的INSTEAD OF触发器来发布与DML语句有关的事件,取而代之的是,对于该视图的基表上的DML操作,你可以使用该视图的基表上的BEFORE或者AFTER触发器来发布事件。
针对事件发布的DML触发器的属性包括系统事件,在SELECT列表中用户定义的列。此类触发器可以使用方案对象的类型和名字,以及函数(比如:UID,USER,USERENV,以及SYSDATE),虚列,以及列来定义简单的条件。通过:OLD以及:NEW可以标识旧列值或者新列值。基于DML语句的触发器包括下面的触发器:
* BEFORE INSERT以及AFTER INSERT触发器,对于插入表中的每个新行将触发该触发器;
*BEFORE UPDATE以及AFTER UPDATE触发器,表中每个被更新的行将引发该触发器;
*BEFORE DELETE以及AFTER DELETE触发器,表中每个被删除的行将引发该触发器;
触发器执行
一个触发器可能处于以下两种状态:
触发器类型 |
定义 |
启用 |
触发器处于启用状态时,如果一个触发语句被声明并且触发限制被判断为TRUE的话,触发操作将被诱发 |
禁用 |
触发器处于禁用状态时,即使触发语句被声明并且触发限制为判断为TRUE的话,触发操作也不会被诱发。 |
对于启用的触发器,oracle自动地执行下面的操作:
*当在一个SQL语句中有多于一个触发器被触发时,oracle会按照计划好的触发顺序来运行每种类型的触发器。首先,语句级别的触发器被触发,之后行级别的触发器被触发;
*oracle执行完整性约束来检查在一个时间点上数据的一致性,能够获取各种触发器对数据的修改,并且保证触发器对数据的修改不违反完整性约束。
*oracle 为查询和约束提供读一致性视图;
*oracle管理触发器之间的依赖关系以及在触发操作代码中所引用的方案对象;
*如果触发器更新在分布式系统中的远程表的话,oracle分两个阶段来提交事务;
*对于给定的语句如果有相同类型的触发器多于一个的话,oracle按照随机的顺序来触发触发器;也就是说对于相同语句中的相同类型的触发器,触发的顺序是不可以指定的。
触发器的执行模式以及完整性约束检查
一个单独的SQL语句能够潜在地触发下面四类触发器:
*BEFORE 行出发器
*BEFORE 语句触发器
*AFTER 行触发器
*AFTER语句触发器
触发语句或者在触发器内的语句能够引起一个或者多个完整性约束检查操作。触发器也能够包含引起其他触发器触发的语句(连锁触发)。Oracle使用下面的执行模式来维护多触发器适当的触发顺序以及约束检查:
1、 运行所有应用到语句上的BEFORE语句触发器;
2、 对SQL语句所影响的所有数据行循环执行以下操作:
a. 执行与SQL语句相关的所有BEFORE行触发器;
b. 锁住行并且变更行,并且执行完整性约束检查。(直到事务被提交时才释放锁资源);
c. 执行与SQL语句相关的所有AFTER行触发器
3、 执行月语句相关的AFTER语句触发器
上述执行模式是可递归的。比如:一个给定的SQL语句能够诱发一个BEFORE行触发器触发,以及一个完整性约束检查。此BEFORE行触发器,可能执行一个更新操作引起一个完整性约束检查以及一个AFTER语句触发器被触发。AFTER语句触发器引起一个完整性约束检查。此示例体现了执行模式的递归性,如下所述:
原始的SQL语句声明:
1、 BEFORE行触发器被触发:
a. AFTER语句触发器被在BEFORE行触发器中的UPDATE所触发
i. AFTER语句触发器的语句执行
ii. 对被AFTER语句触发器被修改的表进行完整性约束
b. BEFORE 行触发器的语句执行
c. 对被BEFORE 行触发器修改的表进行完整性约束
2、 原始的SQL语句被执行
3、 原始的SQL语句的完整性约束被检查
在上述循环中有两个异常:
*当一个触发语句修改了引用完整性约束中的一张表时(或者主键或者外键表),触发操作语句修改另一张表时,只有触发语句会检查完整性约束。这个就使行触发器增强了引用完整性约束。
*由用户提交的DELETE语句所导致的DELETE CASCADE以及DELETE SET NULL操作触发的语句触发器是在用户提交的DELETE语句执行之前以及之后,而不是在这两种操作前后被触发。这能避免这两种操作在语句级触发器执行过程中遇到错误。
触发器的执行模式中的一个重要的属性是触发语句所触发的所有的活动和完整性检查必须成功执行。如果在一个触发器中有一个异常存在,那么该异常不会显式地被处理,由原始的触发SQL语句所触发执行的所有活动,包括被触发器所触发的活动,都被回滚掉 。触发器也不能够违背完整性约束。触发器执行模式考虑了完整性约束并且禁用了那些违反公开的完整性约束的触发器。
比如:在先前的列出的场景中,假设完整性约束被违反了。违反完整性约束的结果是,触发BEFORE行触发器,以及触发AFTER语句触发器的触发语句所做的变更都被回滚。
注:尽管不同类型的触发器上一按照一定的顺序来触发的,但对于相同语句相同类型的触发器是不保证触发的顺序的。比如:对于UPDATE语句的所有BEFORE行触发器不是总是按照相同的触发顺序来触发的。设计好你的应用程序以便应用程序不依赖相同类型的多触发器的触发顺序。
触发器的数据访问
当一个触发器被触发时,在触发操作中所引用的表可能会遭受在其他用户的事务中的SQL语句的修改。在所有的情况中,在触发器中运行的所有SQL语句遵循普通SQL语句所使用的一般规则。如果被触发的触发器需要读取(查询)或者写(更新)一个未提交的事务修改的值时,那么在被触发的触发器操作内的SQL语句使用下面的规则:
*查询访问的是当前被引用的表的读一致视图以及访问的是在同一触发事务中所变更的任何数据
*更新操作必须等待存在的数据锁被释放才能继续进行
PL/SQL触发器的存储
Oracle以编译形式来存储PL/SQL触发器,就象存储过程一样。当CREATE TRIGGER语句提交时,编译过的PL/SQL代码,被称为P代码,被存储在数据库中并且触发器的源代码被从共享池中清除。
触发器的执行
Oracle运行触发器所使用的步骤和存储过程执行的步骤类似。微弱的区别是如果用户有执行触发器的权限的话该用户有权限触发该触发器。除此之外,触发器的验证和运行方式均与存储过程相同。
触发器依赖性的维护
就象存储过程,触发器也依赖被引用的对象。Oracle对在触发操作中被引用的方案对象之间的依赖关系进行自动地管理。触发器所面对的依赖问题和存储过程所面对的问题一样。Oracle 管理触发器对象的方式也与存储过程相同。触发器被插入到数据字典中。