Oracle触发器中必须用:NEW.col := seq_name.NEXTVAL赋值,禁用SELECT ... INTO;因上下文限制,SELECT ... FROM DUAL在12c+多租户环境易报ORA-04091或ORA-00904错误,而:=是PL/SQL赋值语句,绕过SQL引擎解析。

Oracle触发器里必须用:NEW.col := seq_name.NEXTVAL,禁用SELECT ... INTO
直接在BEFORE INSERT FOR EACH ROW触发器里写SELECT seq_name.NEXTVAL INTO :NEW.no FROM DUAL,99%会报错——不是语法错,而是执行上下文问题。Oracle 12c+ 多租户环境下,这种SELECT FROM DUAL会被视为非确定性操作,触发ORA-04091: table is mutating或ORA-00904: invalid identifier。
正确写法只有一条::NEW.no := seq_name.NEXTVAL。这是 PL/SQL 赋值语句,不走 SQL 引擎解析,绕过所有上下文限制。
-
:NEW前的冒号不能漏,否则变成普通变量赋值,不会写入新行 - 必须用
:=,不是= - 序列名大小写要和
CREATE SEQUENCE时完全一致(默认大写)
拼日期前缀时TO_CHAR(SYSDATE, 'YYYYMMDD')要加'fm'修饰符
业务要求“每天重置”,比如生成20260930-0001,本质是把日期部分作为前缀、序号部分独立计数。但TO_CHAR(SYSDATE, 'YYYYMMDD')在不同数据库实例或会话里,可能因NLS_DATE_FORMAT设置不同返回2026-09-30,导致前缀长度不一致、排序失效甚至唯一约束冲突。
安全写法是显式指定格式模型并加'fm':
:NEW.no := TO_CHAR(SYSDATE,'fmyyyyMMdd') || '-' || LPAD(seq_name.NEXTVAL, 4, '0');
-
fm防止 Oracle 在某些格式下自动补空格(如YEAR格式) -
LPAD(, 4, '0')确保位数统一,避免'SO-1'和'SO-10'字符串排序错位 - 别在触发器里查表算当天最大号——高并发下必然重复,序列才是唯一可靠来源
并发下跳号是正常行为,但业务必须提前接受
只要调用了seq_name.NEXTVAL,号就占了。事务回滚、触发器异常退出、甚至连接中断,都不会回收已分配的号。
- Oracle 默认
CACHE 20,实例重启后可能跳几十号;设NO CACHE能减少跳号但性能下降明显 - PostgreSQL 的
nextval()同样不回滚,且多会话并发时无法保证绝对连续 - 如果业务硬性要求「零跳号」,序列 + 触发器方案就不适用,得改用应用层加锁 +
SELECT FOR UPDATE查最大号再 +1
别在AFTER INSERT里尝试更新主表或拼流水号
流水号必须在插入前生成并赋给主键字段,否则主键冲突或空值报错。所以一律用BEFORE INSERT;AFTER INSERT只能改非主键字段,或者写日志,没法补流水号。
更关键的是:别在AFTER INSERT触发器里UPDATE同一张表——必然触发mutating table错误。
- Oracle / PostgreSQL:在
BEFORE INSERT FOR EACH ROW中直接赋值:NEW.no := seq_name.NEXTVAL - MySQL 没原生序列,8.0+ 支持
CREATE SEQUENCE,但触发器里不能调用NEXT VALUE FOR,稳妥做法是用AUTO_INCREMENT+LAST_INSERT_ID() - 所有拼接逻辑必须在
BEFORE阶段完成,且避免嵌套函数(如REPLACE()、多层CONVERT()),每多一层都增加执行计划不稳定风险

















