:new在before insert/update中可写,在after中只读,因before时数据未提交、处于待定状态,after已落盘故赋值报pls-00363错误。

必须用 BEFORE INSERT,不能用 AFTER INSERT —— 因为只有 :NEW 在 BEFORE 中可写,AFTER 里赋值会直接报 PLS-00363 错误。
为什么 :NEW 在 BEFORE 中能改,在 AFTER 中不能?
Oracle 触发器中,:NEW 是一个伪记录,代表即将写入或刚写入的行。在 BEFORE INSERT OR UPDATE 里,它处于“待定状态”,字段可读可写;而到了 AFTER 阶段,数据已落盘,:NEW 只读,强行赋值如 :NEW.phone := '***' 会触发编译错误 PLS-00363,且修改无效。
常见错误现象:
- 写完
AFTER INSERT触发器后执行INSERT,报错PLS-00363: expression 'NEW.PHONE' cannot be used as an assignment target - 误以为
UPDATE能补救:在AFTER里对本表做UPDATE,结果触发递归、死锁或违反一致性约束
BEFORE INSERT 中给字段赋值的写法要点
核心就是直接用 := 给 :NEW.column_name 赋新值,不需要 UPDATE 语句,也不涉及事务控制。
- 语法必须带
FOR EACH ROW,否则无法访问:NEW - 字段名要和表定义完全一致(大小写敏感,尤其启用了双引号标识符时)
- 空值必须显式判断:
IF :NEW.phone IS NOT NULL THEN ... END IF;,否则LEFT(NULL, 3)会抛ORA-06502 - 字符串操作优先用
LEFT()/RIGHT()或SUBSTR(),避免REGEXP_REPLACE()——高并发下正则引擎抖动明显,且\d在含中文字段里可能失准
示例(手机号脱敏):
CREATE OR REPLACE TRIGGER tr_users_mask_phone
BEFORE INSERT OR UPDATE OF phone ON users
FOR EACH ROW
BEGIN
IF :NEW.phone IS NOT NULL AND REGEXP_LIKE(:NEW.phone, '^[0-9]{11}$') THEN
:NEW.phone := CONCAT(CONCAT(LEFT(:NEW.phone, 3), '****'), RIGHT(:NEW.phone, 4));
END IF;
END;
身份证号脱敏要特别注意字节长度
LENGTH() 和 LENGTHB() 行为不同:前者按字符数算,后者按字节数算。UTF-8 下中文占 3 字节,若用 LENGTH() 切 18 位身份证,遇到含中文的字段(比如备注混入)会偏移错位。
- 必须先判空:
IF :NEW.id_card IS NULL OR TRIM(:NEW.id_card) = '' THEN RETURN; END IF; - 用
LENGTHB(:NEW.id_card)区分 15 位(15 字节)和 18 位(18 字节),再截取:SUBSTR(:NEW.id_card, 1, 6) || '********' || SUBSTR(:NEW.id_card, -4) - 末尾可能是
X,所以不能只匹配\d{17}[\dX]—— 实际字段可能存成ID:11010119900101123X,正则根本匹配不上
容易被忽略的兼容性细节
LEFT() 和 RIGHT() 是 Oracle 12cR2+ 引入的确定性函数,主从复制安全;但如果你的库是 11g 或更早,得换成 SUBSTR(:str, 1, 3) 和 SUBSTR(:str, -4)。
另外,所有脱敏逻辑必须包裹在明确的 IF 条件里——不校验格式就直接脱敏,会导致已脱敏过的 138****1234 被二次处理成 138****234,中间星号被吃掉一位。











