在Oracle 12c之前的版本中,想要实现自增主键,必须先创建一个sequence序列对象,再配合触发器在插入数据前自动获取序列值,写法繁琐且性能有损耗。从Oracle 12c开始,数据库原生支持identity列,只需在建表时加一个关键字,就能实现类似MySQL的自增效果,代码量大大减少,维护成本也随之降低。本文将围绕identity列的用法、底层原理以及与传统方案的对比展开详细说明。

一、identity列的基本语法与两种生成方式
identity列的语法在12c及以上版本中直接写在建表语句里,其标准形式如下:
CREATE TABLE t_user ( id NUMBER GENERATED AS IDENTITY PRIMARY KEY, name VARCHAR2(50) );
上面这条语句执行后,id列就成了自增列,插入数据时不需要显式指定id的值。identity列实际上分为两种生成方式,分别是GENERATED ALWAYS AS IDENTITY和GENERATED BY DEFAULT AS IDENTITY,两者在使用上有明显区别。
第一种是Always方式,表示id的值永远由Oracle自动生成,如果插入语句中显式指定了id值,会直接报错ORA-32795错误。示例:
-- Always方式
CREATE TABLE t_user1 (
id NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name VARCHAR2(50)
);
INSERT INTO t_user1 (name) VALUES ('张三'); -- 成功
INSERT INTO t_user1 (id, name) VALUES (100, '李四'); -- 报错 ORA-32795第二种是By Default方式,表示默认由Oracle自动生成,但也允许用户手动插入指定的值,灵活性更高。当表中已有数据迁移或需要手工补数据时,这种方式更实用。如果指定了START WITH和INCREMENT BY参数,还可以控制起始值和步长:
CREATE TABLE t_user2 (
id NUMBER GENERATED BY DEFAULT AS IDENTITY
(START WITH 1000 INCREMENT BY 1) PRIMARY KEY,
name VARCHAR2(50)
);
INSERT INTO t_user2 (name) VALUES ('王五'); -- id为1000
INSERT INTO t_user2 (id, name) VALUES (5000, '赵六'); -- 手动指定,成功需要注意的是,By Default方式手动插入值后,序列并不会自动跳过这个值,后续自动生成的id可能与手动插入的值冲突。如果希望手动插入时也能让序列感知并继续,可以使用GENERATED BY DEFAULT ON NULL AS IDENTITY,当插入NULL时依然走自增逻辑。
二、identity列的底层原理:其实是sequence的封装
identity列并不是凭空实现的自增机制,Oracle在内部会自动创建一个sequence对象来支撑它。查看数据字典可以发现,系统自动生成了一个以ISEQ$$_开头的序列:
SELECT sequence_name, last_number, increment_by FROM user_sequences WHERE sequence_name LIKE 'ISEQ$$_%';
这个内部序列对用户来说是隐藏管理的,不能直接修改它的属性。如果需要调整自增的起始值或步长,必须通过ALTER TABLE ... MODIFY语句来实现,例如:
ALTER TABLE t_user2 MODIFY (id GENERATED BY DEFAULT AS IDENTITY (START WITH 2000 INCREMENT BY 2));
此外,identity列的值在回滚或事务失败后同样会出现空洞。这是因为sequence本身是非事务性的,取出的值即使没有提交成功也不会归还,这是所有基于sequence方案共同的特点,业务设计时不能假设id完全连续。
由于identity列本质上依赖内部sequence,它的取值性能也和sequence的缓存机制相关。默认情况下identity列使用CACHE 20,在批量插入场景下性能表现良好。如果遇到RAC环境下的高并发插入,可以考虑增大缓存值以减少实例间争用,同样通过ALTER语句调整。
三、与传统sequence加触发器方案的对比
在12c之前,经典的实现方式是先建sequence,再建一个BEFORE INSERT触发器,代码如下:
CREATE SEQUENCE seq_user START WITH 1 INCREMENT BY 1;
CREATE OR REPLACE TRIGGER trg_user BEFORE INSERT ON t_user3
FOR EACH ROW
BEGIN
SELECT seq_user.NEXTVAL INTO :new.id FROM dual;
END;
/
INSERT INTO t_user3 (name) VALUES ('测试');
SELECT seq_user.CURRVAL FROM dual; -- 查询当前值两种方案各有优劣。identity列的优势在于语法简洁、无需维护额外的触发器对象、可移植性思考更接近MySQL标准写法,迁移成本低;劣势是灵活性略差,无法在复杂场景下复用同一个序列。而传统方案的优势是sequence可以被多张表共享,触发器里还可以加入更复杂的逻辑,缺点是对象数量多、触发器在高并发插入时带来额外开销,且SQL语句不够直观。
还有第三种做法,即不使用触发器,而是在INSERT语句中直接写seq_user.NEXTVAL,从12c开始甚至可以省略FROM dual。这种方式介于两者之间,既保留了sequence的复用能力,又减少了触发器的性能损耗,是很多老系统持续使用的方案。
四、常见问题与注意事项
首先是版本兼容问题,identity列只支持Oracle 12c及以上版本,如果脚本需要在11g环境运行,建表语句会直接报语法错误,编写DDL时要注意目标库版本。
其次是删除和修改的限制。不能直接删除identity列上的自增属性以外的东西而不影响数据,若要去掉自增特性,可以执行ALTER TABLE t_user2 MODIFY (id DROP IDENTITY),此时列和数据的保留但不再自增。删除表时,内部的ISEQ$序列会随表一起自动清理,无需手工干预。
最后是数据迁移场景。如果从旧表迁移数据到带identity列的新表,建议使用By Default方式,先把旧数据连同id一起插入,再通过ALTER语句把START WITH调整到最大id之后,避免后续自增值与历史数据冲突。掌握这些细节,就能在实际项目中灵活运用identity列,写出更简洁高效的建表脚本。
Oracle identity列Oracle自增主键sequence序列修改时间:2026-08-31 15:00:55