Oracle数据库如何通过触发器自动生成主键ID_结合Sequence序列

2026-05-22数据库145668

Oracle 11g及更早版本必须用SEQUENCE+TRIGGER模拟自增主键;建序列时START WITH需与表中当前最大ID对齐(如MAX(id)=999则设START WITH 1000),并显式指定INCREMENT BY 1、NOCACHE;触发器须含FOR EACH ROW和WHEN (:NEW.id IS NULL)条件,避免ORA-01400或覆盖业务传值。

Oracle 11g 及更早版本不支持 AUTO_INCREMENT,也未引入 IDENTITY 列(12c 起才有),所以必须用 SEQUENCE + TRIGGER 组合来模拟自增主键——这不是“ workaround”,而是生产环境的标准做法。

创建 Sequence 时必须校准 START WITH 值

如果表里已有数据,START WITH 设小了会报 ORA-00001: unique constraint violated;设大了则浪费号段。关键不是“从 1 开始”,而是和当前最大 ID 对齐:

  • 先查当前最大值:SELECT NVL(MAX(id), 0) FROM your_table
  • 若结果是 999,则建序列时写 START WITH 1000
  • INCREMENT BY 1 必须显式写出,避免误继承 session 级默认值
  • NOCACHE 更稳妥:RAC 环境或异常重启时不会丢号;若选 CACHE 20,得接受小概率跳号(比如插入 3 行后事务回滚,下次 NEXTVAL 直接跳到 24)

触发器里漏掉 FOR EACH ROW 或判断条件就白写了

常见错误是触发器编译通过但插入仍报 ORA-01400: cannot insert NULL,问题几乎都出在这两处:

  • 没写 FOR EACH ROW → 触发器只在语句级生效,不作用于每一行
  • 没加 WHEN (:NEW.id IS NULL) 或等价的 IF :NEW.id IS NULL THEN ... END IF; → 应用层偶尔传了 ID(比如导入旧数据),触发器却强行覆盖,可能破坏业务逻辑
  • 别用 :OLD.id 做判断,INSERT:OLD 为空,会报 ORA-04082: NEW or OLD references not allowed in table level triggers

INSERT 显式指定 ID 不会导致 Sequence 跳号,但会“浪费”一个值

emp_id_seq.NEXTVAL 是独立递进的,它不感知你是否用了这个值。例如:

oracle知识库

oracle知识库下载

下载

  • 当前 CURRVAL = 105
  • 你执行 INSERT INTO emp (id, name) VALUES (100, 'Alice')
  • 下一次 NEXTVAL 仍是 106,不是 101

这意味着:如果你依赖 ID 连续性做单据编号、审计追踪,Sequence 就不适合——它只保证唯一和递增,不保证连续。真要连续,得用应用层锁表+查最大值++1,但并发下性能差、易死锁。

并发插入时 CURRVAL 不可靠,别用它取刚插入的 ID

多个会话同时插入时,emp_id_seq.CURRVAL 返回的是“本会话最近一次调用 NEXTVAL 的值”,不是“刚插进去那条的 ID”。比如:

  • 会话 A 执行 INSERT → 触发器取走 105
  • 会话 B 紧接着也插入 → 取走 106
  • 会话 A 此时查 CURRVAL,得到的是 106(B 刚取的),不是自己的 105

正确方式是用 RETURNING INTO(如 INSERT ... RETURNING id INTO :v_id),或在触发器里把 NEXTVAL 结果存入包变量再读——但后者仅限同会话内安全。

标签: