1986年,Oracle 5.1版本发布。没人注意到手册里多了一章叫"Triggers"的附录——一个让数据库自己"长脑子"的机制。30多年后,这个设计仍在让新手DBA踩坑:为什么我的数据插进去了,日志却没记?为什么删了行,关联表还在?

答案藏在两行代码的顺序里。BEFORE和AFTER,差一个字,执行流天差地别。

触发器是什么:数据库的自动开关

触发器是什么:数据库的自动开关

想象你家的智能插座。不是你去按开关,而是"温度超过30度"这个事件本身触发断电——触发器(数据库触发器)就是这个逻辑,只是它活在Oracle、MySQL、PostgreSQL的引擎里。

它有三条铁律:存进数据库、自动执行、绑定特定事件。你不能像调用函数那样手动喊它出来,它只认事件不认人。

事件分四类,但90%的DBA这辈子只碰前两种:INSERT(插入)、UPDATE(更新)、DELETE(删除),以及它们的组合。DDL触发器?系统级触发器?那是给架构师准备的玩具。

BEFORE vs AFTER:时间线里的暗战

BEFORE vs AFTER:时间线里的暗战

这是最容易被低估的设计。Oracle给你两个挂钩点:数据落地前,或数据落地后。

BEFORE触发器的典型用法是做数据清洗。用户传进来的工资是5000,你想自动补个部门编码?在这里改。INSERT语句还没真正把行写进磁盘,你有机会重写那行数据。

代码长这样:

```sql CREATE OR REPLACE TRIGGER trg_before_insert BEFORE INSERT ON employees FOR EACH ROW BEGIN DBMS_OUTPUT.PUT_LINE('Before inserting data'); END; /```

执行顺序:触发器先跑,数据后入库。如果触发器里抛异常,整个INSERT会回滚——像安检门,没过就别想登机。

AFTER触发器则完全不同。数据已经落盘,你想做关联操作、记日志、发通知?选这里。但别试图修改正在被插入的那行,时机已过。

```sql CREATE OR REPLACE TRIGGER trg_after_insert AFTER INSERT ON employees FOR EACH ROW BEGIN DBMS_OUTPUT.PUT_LINE('After inserting data'); END; /```

执行顺序:数据先进库,触发器后跑。这像是酒店入住后的欢迎短信——房间已经给你了,后续服务跟上。

FOR EACH ROW:行级与语句级的隐秘成本

FOR EACH ROW:行级与语句级的隐秘成本

上面两个例子都带了FOR EACH ROW子句。这意味着:插入100行,触发器跑100次。

去掉它呢?触发器只跑1次,不管语句影响多少行。批量操作时,这个选择能差出两个数量级的性能。

我见过一个血案:某电商大促,订单表用了行级AFTER触发器做库存扣减。平时没问题,促销时每秒万级写入,触发器把CPU吃满,核心交易挂了4分钟。后来改成语句级+批量处理,同样的逻辑,TPS从1200飙到18000。

Oracle文档里这个细节藏在第15章第3节,没人提醒你。

为什么不能手动调用:设计哲学的代价

为什么不能手动调用:设计哲学的代价

这是触发器跟存储过程(Stored Procedure)最本质的区别。过程是你手里的遥控器,触发器是墙上的声控灯——它只听环境的,不听你的。

好处很明显:业务逻辑和数据约束绑在一起,应用层想绕过?没门。坏处同样明显:调试像在黑箱里摸鱼,你不知道哪条语句触发了什么,连锁反应藏得很深。

有个老梗:DBA最讨厌的两件事,一是别人写的触发器,二是自己写的触发器。三年前埋的BEFORE UPDATE,三年后业务改需求,排查了六个小时才发现是它在中间改了字段值。

Oracle不是没给调试工具。DBMS_OUTPUT.PUT_LINE能打印,但生产环境常关着;触发器里可以抛自定义异常,但堆栈信息往往指不到源头。这种"自动"的代价,是可控性的让渡。

1986年那个附录的作者叫Bruce Scott,Oracle早期的技术写手。他在第一版文档里写了句话:"Triggers should be used sparingly." 慎用。30多年过去,这句话仍在被违反。

你现在用的数据库里,有多少触发器在暗处运行?上次审计是什么时候?