必须用 BEFORE 触发器,因AFTER触发时DML已提交,RAISE_APPLICATION_ERROR无法回滚;BEFORE可在约束检查前中止操作,不生成undo/redo;需显式指定NLS_DATE_LANGUAGE=AMERICAN避免周几判断错误。

必须用 BEFORE INSERT OR UPDATE OR DELETE 触发器,且逻辑必须硬编码在触发器体内——不能查配置表、不能调用含事务的函数,否则会报 ORA-04092 或递归 SQL 错误。
为什么只能用 BEFORE 触发器
Oracle 没有 AFTER 级别的“拦截”能力:AFTER 触发器执行时,DML 已完成,数据已落库。即使抛错,用户连接仍存在,数据变更不可逆。而 BEFORE 触发器在语句解析后、约束检查前执行,此时 RAISE_APPLICATION_ERROR 可彻底中止整个 DML 操作,不生成任何 undo/redo。
- 别信“AFTER + ROLLBACK”能补救——Oracle 不允许在 AFTER 触发器里回滚主事务
- 语句级(无
FOR EACH ROW)足够,除非你要按行判断(比如只拦 UPDATE 某些字段) - DBA 账号如
SYS、SYSTEM必须放行,否则维护通道被锁死
工作日+时段判断怎么写才不出错
核心是绕开 NLS 设置陷阱:Oracle 的 TO_CHAR(SYSDATE, 'D') 返回值依赖会话语言(周日可能是 1 或 7),必须显式指定 NLS_DATE_LANGUAGE=AMERICAN。小时部分用 HH24 字符串比较,避免数值边界歧义。
- 周一到周五 =
TO_CHAR(SYSDATE, 'D', 'NLS_DATE_LANGUAGE=AMERICAN') NOT IN ('1','7') - 工作时间 8:00–17:59 =
TO_CHAR(SYSDATE, 'HH24') BETWEEN '08' AND '17'(字符串比对自动补零,'8'会变成'08') - 跨零点?不用管——工作日规则天然排除了周末,无需像 MySQL 那样写
>=22 OR - 测试时发现总被拦?检查当前会话的
NLS_TERRITORY,临时加ALTER SESSION SET NLS_TERRITORY=AMERICA;验证
触发器里最常踩的三个坑
不是语法错,而是 Oracle 底层机制导致的静默失效或报错:
-
RAISE_APPLICATION_ERROR前做了INSERT INTO log_table?没加PRAGMA AUTONOMOUS_TRANSACTION就等于白写,主事务一回滚,日志全丢 - 用了
TO_CHAR(SYSDATE, 'DAY')(返回中文“星期一”)做判断?本地化字符串无法稳定匹配,IN ('星期六','星期日')在英文环境直接报错 - 在触发器里调了自定义函数,而该函数内部有
SELECT ... FROM dual?这算递归 SQL,轻则 ORA-00604,重则锁死连接池
真正可靠的触发器,代码不超过 15 行,只做两件事:判时间、抛错误。任何额外逻辑(日志、通知、调包)都得剥离出去,用自治事务单独封装——否则不是性能问题,是稳定性问题。











