必须用FOR EACH ROW才能访问:old和:new伪记录;当前用户应通过SYS_CONTEXT('USERENV', 'SESSION_USER')获取,而非USER或CURRENT_USER;:new仅用于INSERT/UPDATE,:old仅用于UPDATE/DELETE。

行级触发器里怎么拿到当前用户和数据变更前后的值
必须用 FOR EACH ROW 才能访问 :old 和 :new;没这句就只能写语句级触发器,拿不到单行数据。当前用户用 SYS_CONTEXT('USERENV', 'SESSION_USER') 获取,不能用 USER 或 CURRENT_USER——它们在触发器里可能返回定义者而非调用者。
常见错误:在 INSERT 里引用 :old.deptno,结果报 ORA-04082: NEW or OLD references not allowed in table level triggers;或者在没加 FOR EACH ROW 的触发器里写 :new,直接编译失败。
-
:new在INSERT和UPDATE中有效,:old在UPDATE和DELETE中有效 - 修改
:new值只对BEFORE触发器有意义(比如自动填充创建时间),AFTER里改无效 - 如果表字段名含大小写或特殊字符,引用时必须加双引号,如
:new."OrderID"
如何阻止用户修改不属于自己的记录
典型场景:员工只能更新自己部门的数据,或销售员只能改自己名下的订单。核心是把业务规则嵌入 BEFORE UPDATE 触发器,并用 RAISE_APPLICATION_ERROR 中断操作。
注意别漏掉 UPDATE 的字段判断——比如只限制 salary 字段被改时校验,其他字段允许自由更新,就得在 WHEN 子句里写 UPDATING('salary'),否则所有 UPDATE 都会触发检查。
- 不要在触发器里查同表(比如
SELECT COUNT(*) FROM emp WHERE ...),容易引发ORA-04091: table is mutating - 用
SYS_CONTEXT('USERENV', 'CLIENT_IDENTIFIER')可获取应用层传入的业务用户(比SESSION_USER更准) - 权限检查逻辑尽量轻量,避免调用复杂函数或远程查询,否则拖慢 DML 性能
为什么不用 VPD 而选触发器做行级控制
VPD(虚拟专用数据库)确实更适合纯读权限隔离,但触发器能覆盖 INSERT/UPDATE/DELETE 全流程,且不依赖 DBMS_RLS 包和策略函数——这对老版本 Oracle(如 10g)或受限权限环境更实用。
真正容易踩的坑是:误以为触发器能替代权限模型。它只是“事后拦截”,不是“事前授权”。如果用户有 UPDATE ANY TABLE 系统权限,触发器拦得住数据,但拦不住执行计划暴露、统计信息泄露等风险。
- 触发器无法防止
SELECT绕过(除非配合视图+INSTEAD OF触发器) - DDL 操作(如
TRUNCATE)不触发 DML 触发器,需额外用系统触发器监控 - 批量
UPDATE时,行级触发器逐行执行,可能比单条 SQL 慢数倍,压测时务必验证
登录触发器限制客户端工具和 IP 的写法要点
系统级触发器如 AFTER LOGON ON DATABASE 是实现连接层控制的唯一方式,但它不区分具体对象,只作用于整个实例。
关键点在于:必须用 V$SESSION 或 SYS_CONTEXT 获取实时会话信息,且不能在触发器里提交事务(否则报 ORA-00103)。日志写入要用 DBMS_SYSTEM.KSDWRT 或外部表,别用普通 INSERT。
-
SYS_CONTEXT('USERENV', 'MODULE')返回客户端工具名(如SQL*Plus、plsqldev.exe),但部分 JDBC 驱动会返回空或固定值 - IP 地址在 NAT 环境下可能不准,建议结合
CLIENT_IDENTIFIER或应用层 token 辅助判断 - 触发器抛错后连接直接断开,无法自定义友好提示——错误信息会原样返回给客户端
EXECUTE 权限,且这些权限不能来自角色——必须是直接授予的。否则触发器编译通过,运行时报 ORA-01031: insufficient privileges。


















