Oracle触发器中必须用SELECT my_seq.NEXTVAL INTO :NEW.id FROM dual赋值,不可直接赋值;且需加WHEN (:NEW.id IS NULL)防止覆盖手动指定ID。

触发器里不能直接赋值 NEXTVAL,必须用 SELECT ... INTO ... FROM dual
很多人从 MySQL 或应用层逻辑迁过来,下意识写 :NEW.id := my_seq.NEXTVAL,结果编译报错 PLS-00382: expression is of wrong type。Oracle 的行级触发器中,:NEW 伪记录的列不能用赋值运算符直接接收序列值——它只接受 SQL 查询结果。
正确写法只有一种:SELECT my_seq.NEXTVAL INTO :NEW.id FROM dual;。漏掉 FROM dual 会触发 ORA-00923: FROM keyword not found;写成 SELECT my_seq.NEXTVAL FROM dual 不带 INTO 则无效果,主键仍为 NULL 或默认值。
- 必须在 PL/SQL 块内执行,不能简化为单条 SQL 赋值语句
-
dual是强制要求,哪怕只是取一个标量值 - 如果需要生成复合逻辑(比如拼前缀),得先
SELECT出数值,再拼接、再赋给:NEW.id
WHEN (NEW.id IS NULL) 不是可选项,而是防止覆盖业务数据的生命线
不加这个条件,只要触发器启用,所有 INSERT 都会强行重写 id 字段——哪怕你显式传了 INSERT INTO t(id, name) VALUES(999, 'test'),也会被覆盖成序列下一个值。这在迁移旧系统、补录数据、联调测试时极易引发主键冲突或业务 ID 错乱。
更隐蔽的问题是:应用层传 NULL 和没传字段,在 Oracle 触发器里都表现为 :NEW.id IS NULL。所以如果你的 ORM 默认把未填字段设为 NULL,那 WHEN 条件就刚好兜住;但若业务逻辑本意是“允许手动指定 0 或负数 ID”,就得改成 WHEN (:NEW.id IS NULL OR :NEW.id 这类自定义判断。
- 不加
WHEN条件 = 每次插入都无条件劫持主键 - 大小写敏感:如果建表时用双引号定义了
"ID",触发器里也必须写:NEW."ID" - Oracle 12c+ 支持
IDENTITY列,但老项目升级时,现有触发器不会自动失效,得人工确认是否共存
需要拼接字符串或带业务规则的 ID?先查再拼,别在 SELECT INTO 里硬塞函数
比如要生成类似 'USR_20260827_000123' 这种带日期和序列号的主键,不能写成 SELECT 'USR_' || TO_CHAR(SYSDATE, 'YYYYMMDD') || '_' || LPAD(my_seq.NEXTVAL, 6, '0') INTO :NEW.id FROM dual——这会导致每次 INSERT 都消耗一个序列值,但拼接失败时序列已跳号,且无法回滚。
稳妥做法是分两步:先取序列值,再用 PL/SQL 变量拼接。这样既能控制错误分支,也能复用该值做日志或关联操作。
DECLARE v_seq_val NUMBER; BEGIN SELECT my_seq.NEXTVAL INTO v_seq_val FROM dual; :NEW.id := 'USR_' || TO_CHAR(SYSDATE, 'YYYYMMDD') || '_' || LPAD(v_seq_val, 6, '0'); END;
- 避免在
SELECT表达式里混用序列和函数,否则跳号不可控 - 日期部分若依赖事务时间点,用
SYSDATE;若需插入时刻快照,考虑LOCALTIMESTAMP - 拼接长度超限?提前加
LENGTH()校验或用RAISE_APPLICATION_ERROR抛出自定义错误
已有数据的表加自增,ALTER SEQUENCE ... RESTART WITH 必须做,且要查准最大值
假设表里已有 5000 条记录,最大 id 是 4999,新建序列却从 1 开始,第一次插入就报 ORA-00001: unique constraint violated。这时候光改触发器没用,必须让序列“追上”当前最大值。
安全做法是:先查 SELECT NVL(MAX(id), 0) FROM my_table,再执行 ALTER SEQUENCE my_seq RESTART WITH 5000(Oracle 12c+)。低于 12c 的版本得用三步法:INCREMENT BY 调整步长 → NEXTVAL 消耗一次 → 再调回原步长。
- 别信
LAST_NUMBER视图值——它反映的是缓存末尾,不是真实已用最大值 - 高并发场景下,查最大值和重启序列之间可能有新插入,建议在维护窗口停写入,或加应用层锁
- 如果表有分区或物化视图依赖该主键,重置序列后记得检查依赖对象状态
INSERT /*+ APPEND */ 或批量导入工具(如 SQL*Loader)——这些场景往往绕过应用校验,但逃不过触发器。如果你的自定义逻辑含查询其他表、调用函数或异常处理,务必压测吞吐量,避免成为批量插入瓶颈。


















