SQL 触发器

  • Post author:
  • Post category:其他

触发器( trigger )是作为对数据库修改的连带效果由系统自动执行的一条语句。为了定义一个触发器,我们必须:

  • 指明什么时候执行触发器。这被拆分为引起触发器被检测的一个事件触发器继续执行所必须满足的一个条件
  • 指明当触发器执行时所采取的动作

一旦我们把一个触发器输人数据库中,只要发生指定的事件并且满足相应的条件,数据库系统就负责去执行它。

对触发器的需求

**触发器可以被用来实现特定的完整性约束,这些约束不能使用 SQL 的约束机制来指定。**触发器还是一种有用的机制,用来当满足特定条件时对人们发出警报自动开始执行特定的任务。作为一个示例,我们可以设计一个触发器:只要一个元组被插入 takes 关系中,就在 student 关系中更新选课的学生所对应的元组,把该课的学分数值加入这名学生的总学分中。作为另一个示例,假设一个仓库希望维护每种物品的最低库存量,当一种物品的库存量低于最低水平时,可以自动下单。在更新一种物品的库存时,触发器会比较这种物品的当前库存量和它的最低库存量,并且如果库存量等于或低于最低库存量,就会创建一份新的订单。
请注意,触发器通常不能执行数据库以外的更新,因此,在补充库存的示例中,我们不能用一个触发器去在外部世界中下订单,而是在存放订単的关系中添加一条订单记录。我们必须创建一个单独的、永久运行的系统进程来周期性地扫描该关系并下单。某些数据库系统提供了内置的支持,可以通过这种方法从 SQL 査询和触发器中发送电子邮件。


SQL 中的触发器

现在我们来考虑如何在 SQL 中实现触发器。我们在这里介绍的是由 SQL 标准所定义的语法,但是大多数数据库实现的是这种语法的非标准版本。尽管这里所述的语法可能不被这些系统所支持,但是我们所阐述的概念是对于不同实现都适用的。我们将在注释中讨论标准的触发器实现。在每个系统中,触发器语法都基于该系统中对函数和过程进行编码的语法
下图展示了如何使用触发器来确保 section 关系的 time_slot_id 属性上的引用完整性。图中第一个触发器的定义指明该触发器在任何一次对 section 关系的插入之后被启动,并且它确保所插入的 time_slot_id 值是合法的。 SQL 插入语句可以向关系中插入多个元组,而触发器代码中的 for each row 子句可以随后显式地在被插入的每一行上进行迭代。 referencing new row as 子句创建了一个 nrow 变量(称为过渡变量( transition variable )),它用来存储所插入行的值。
在这里插入图片描述
when 语句指定了一个条件。系统仅对于满足该条件的元组才会执行触发器体中的其余部分。 begin atomic … end 子句用来将多条 SQL 语句汇集成单条复合语句。不过在我们的示例中只有一条语句,它对引起触发器执行的事务进行回滚。因此,违背引用完整性约束的任何事务都将被回滚,从而确保数据库中的数据满足该约束。
只检查插入时的引用完整性是不够的,我们还需要考虑对 section 的更新,以及对被引用表 timeslot 的删除和更新操作。上图中定义的第二个触发器考虑的是对 time_slot 删除的情况。这个触发器检查要么被删除元组的 time_slot_id 还在 time_slot 中,要么在 section 中不存在包含这个特定 timeslot_id 值的元组,否则将违背引用完整性。
为了保证引用完整性,我们还必须为处理 section 和 time_slot 的更新来创建触发器;我们接下来将介绍如何在更新时执行触发器,不过,我们将这些触发器的定义留给读者作为练习。对于更新来说,触发器可以指定是哪些属性的更新导致触发器的执行,而其他属性的更新却不会让它执行。例如,为了指定在更新 takes 关系的 grade 属性之后执行一个触发器,我们写作:

after update of takes on grade 

referencing old row as 子句可以用来创建一个变量,它存储一个已更新或已删除的行的旧值。 referencing new row as 子句除了用于插入之外,还可以用于更新。
下图展示了当对 takes 关系中元组的 grade 属性进行更新时,如何使用触发器来使 student 元组的 tot_cred 属性值保持最新。只有当 grade 属性从空值或者‘ F ’值被更新为表示课程已成功修完的分数时,该触发器才会执行。除了 nrow 变量的使用之外,更新( update )语句是正规的 SQL 语法。
在这里插入图片描述
作为使用触发器的另一个示例,当删除一个 student 元组的操作发生时,需要检查在 takes 关系中是否存在与该学生相关的项,并且如果有则删除这些项。
许多数据库系统支持各种其他的触发事件,比如当一个用户(应用程序)登录到数据库(即打开一个连接)的时候、系统停止的时候或者系统设置改变的时候。
触发器可以在事件(插入、删除或更新)之前被激活,而不仅是在事件之后被激活。在事件之前执行的触发器可以作为避免非法更新、插入或删除的额外约束。为了避免执行非法操作而产生错误,触发器可以采取措施来纠正问题,使更新、插入或删除变得合法化。例如,假设我们想把一位教师插人一个系中,但该系的名称并未出现在 department 关系中,那么触发器就可以在插入操作产生外码冲突之前针对该系的名称往 department 关系中插人一个元组。作为另一个示例,假设所插入分数的值为空白,这可能表示分数缺失。我们可以定义一个触发器,将这个值用空( null )值来替换。可以使用 set 语句来执行这样的修改。
在这里插入图片描述
我们可以针对引起插入、删除或更新的整条 SQL 语句执行单个操作,而不是针对每个受影响的行执行一个操作。为了做到一点,我们用 for each statement 子句来替代 for each row 子句。然后可以用 referencing old table as 子句或 referencing new table as 子句来指代包含所有受影响行的临时表(称为过渡表( transition table ))。过渡表不能用于 before 触发器,但是它们可以用于 after 触发器,无论是语句触发器还是行触发器。这样,在过渡表的基础上,单条 SQL 语句就可以用来执行多个操作。
触发器可以被禁用或启用;在缺省情况下,当触发器被创建时它们是启用的,但是可以通过使用 alter trigger trigger_name disable (某些数据库使用其他可替代的语法,比如 disable trigger trigger_name )将其禁用。已被禁用的触发器可以重新启用。通过使用命令 drop trigger trigger_name 还可以删除触发器,该命令将其永久移除。


何时不用触发器

触发器有许多很好的用途,例如我们刚刚所看到的那些,然而有一些用途最好用别的可替代技术来实现。比如说,我们可以通过使用触发器而不是使用级联特性来实现外码约束的级联删除( on delete cascade )特性。然而这样不仅需要完成更多的工作,而且它使得数据库中实现的约束集合对于数据库用户来说要难以理解得多
作为另一个示例,可以用触发器来维护物化视图。例如,如果我们希望能够快速访问到每门课所注册的学生总数,可以通过创建一个关系来实现这个功能:

section_registration(course_id,sec_id,semester,year,total_students)

它由以下查询来定义:

select course_id,sec_id,semester,year,count(ID) as total_students from takes 
group by couseid,sec_id,semester,year;

必须通过 takes 关系的插入、删除或更新上的触发器,来将每门课的total_students的值维护到最新状态。在对section_registration进行元组的插人、更新或删除时可能需要这样的维护,并且必须相应地编写触发器。
然而,许多数据库系统现在支持的物化视图是由数据库系统来自动维护的。其结果是,没必要编写触发器代码来维护这样的物化视图
触发器已经被用来维护数据库的备份或者副本在每个关系上可以针对插入、删除或更新来创建一组触发器,以将变化记录在称为 change 或 delta 的关系中。一个单独的进程会将这些变化拷贝到数据库的副本中。然而,现代的数据库系统提供了内置的数据库复制工具,使得在大多数情况下不必为了复制而使用触发器
触发器的另一个问题在于,当数据从备份副本中加载,或者当一个站点处的数据库更新被复制到备份站点的时候,触发器动作的意外执行。在这样的情况下,触发器动作已经执行了,并且通常不应该再次执行。在加载数据的时候,可以显式地禁用触发器对于可能要接管主系统的备份复制系统来说,必须首先禁用触发器,然后在备份站点接管了主系统的业务后再启用触发器。作为另一种可选方案,一些数据库系统允许触发器被指定为 not for replication ,它保证不会在数据库复制期间在备份站点上执行触发器。另一些数据库系统提供了一个系统变量,它指明该数据库是一套副本,数据库动作在其上是重演的;触发器体会检查这个变量,如果它为真则退出执行。这两种解决方案都不需要显式地禁用和启用触发器。
编写触发器时应该特别小心,因为运行时检测出的触发器错误会导致触发该触发器的动作语句失败。此外,一个触发器的动作可以触发另一个触发器在最坏的情况下,这甚至会导致无限的触发链。例如,假设一个关系上的插入触发器有一个动作,它引发同一关系上的另一个(新的)插人,该新插入动作随后又触发另外一个插入动作,并如此无穷循环下去。有些数据库系统限制了这种触发链的长度(例如最长到16或32),并把更长的触发链视为一个错误另一些系统将任何这样的触发器标记为错误:该触发器试图引用的关系的修改会导致该触发器首先执行
触发器可以起到非常有用的作用,但是当存在其他备选方案时最好避免使用触发器许多触发器的应用程序都可以通过存储过程的恰当使用来替换


版权声明:本文为weixin_45798993原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。