主表更新同步子表必须用after update,因before中修改子表可能引发递归或ora-04091错误;安全做法是for each row记录id至集合,再由after statement批量更新子表。

触发器里用 AFTER UPDATE 还是 BEFORE UPDATE?
主表更新时同步改子表,必须用 AFTER UPDATE。用 BEFORE UPDATE 会导致子表修改触发主表再次更新,可能引发递归或 ORA-04091(表正在变化)错误。除非你明确需要在主表提交前预校验子表状态,否则别碰 BEFORE —— 它不解决级联更新问题,反而增加死锁风险。
怎么安全地在触发器里更新子表而不报 ORA-04091?
ORA-04091 出现在触发器中直接查/改同一事务中被修改的表时。解决办法只有两个:
- 把子表操作放到自治事务(
PRAGMA AUTONOMOUS_TRANSACTION)里 —— 但会丢失事务一致性:主表回滚,子表已提交,级联就断了; - 更稳妥的做法:不用触发器直接改子表,改用
FOR EACH ROW触发器收集主表变更的:OLD.id和:NEW.id,存进一个包级集合(如TYPE t_id_list IS TABLE OF NUMBER),再在AFTER STATEMENT级触发器里统一批量更新子表 —— 这样避开了行级上下文对子表的直接访问。
:OLD 和 :NEW 在多行更新时怎么用才不漏数据?
PL/SQL 触发器默认是语句级的,但级联更新必须感知每行变化。所以必须声明为 FOR EACH ROW,否则 :OLD/:NEW 不可用。注意两点:
- 如果主表一次
UPDATE影响 100 行,FOR EACH ROW触发器会执行 100 次 —— 别在里面写单行UPDATE child_table SET ... WHERE parent_id = :OLD.id,性能会崩; - 正确做法是在行级触发器里只做“记账”:把
:OLD.id加入全局集合;然后在配套的AFTER STATEMENT触发器里,用UPDATE child_table SET ... WHERE parent_id IN (SELECT COLUMN_VALUE FROM TABLE(g_id_list))一次性更新; - 记得在
AFTER STATEMENT触发器末尾清空集合,否则下次执行会累积脏数据。
为什么别用触发器实现业务级级联逻辑?
触发器级联看着省事,实际埋坑很多:
- 调试困难:出错时堆栈不显示触发器调用链,
DBMS_OUTPUT在自治事务里还看不到; - 维护成本高:别人
UPDATE parent时根本想不到背后还有一堆子表被静默改了; - 迁移/同步受限:逻辑复制、GoldenGate 或某些 ORM 框架可能跳过触发器;
- 真正该用触发器的场景其实很窄:比如审计日志、简单状态标记(
last_modified)、或强一致性约束检查 —— 而不是替代应用层的业务逻辑。
如果子表更新涉及计算、权限判断或外部服务调用,一定挪到存储过程或应用代码里。触发器只负责最薄一层“反应式同步”,且必须配完整测试用例覆盖单行/多行/回滚场景。










