在设计数据库表时,您通常需要一个唯一标识符作为主键,例如用户ID、订单号等。手动管理这些ID既繁琐又容易出错。为了解决这一问题,PolarDB PostgreSQL版(兼容Oracle)提供了序列和自动生成列两种机制。它们都能自动、安全地生成唯一的数字序列,简化了主键管理,并确保了数据的一致性。
功能简介
PolarDB PostgreSQL版(兼容Oracle)提供了多种生成唯一序列值的方式,以满足不同场景的需求,并高度兼容Oracle数据库。
传统序列
这是一个独立的数据库对象,通过CREATE SEQUENCE
创建。在插入数据时,您需要通过调用NEXTVAL
伪列(例如my_seq.NEXTVAL
)显式地从序列中获取下一个值并插入到表中。这种方式最灵活,序列和表是解耦的。自动生成列:
这是Oracle 12c引入的特性,它将序列隐式地与表中的某一列绑定。在创建表时,您只需在列定义上添加GENERATED ... AS IDENTITY
子句,数据库就会在插入新行时自动为该列生成值,无需在INSERT
语句中显式处理。这种方式更加便捷,符合现代数据库的设计趋势。
自动生成列根据其行为策略,又分为以下三种类型:
GENERATED ALWAYS AS IDENTITY
:强制自动生成。系统总是为该列生成值,您不能在INSERT
语句中为该列提供显式值,否则会报错。这种策略最严格,能确保ID完全由数据库控制。GENERATED BY DEFAULT AS IDENTITY
:默认自动生成。只有在INSERT
语句中未指定该列时,系统才会为其生成值。如果您提供了具体值,系统将使用您提供的值。但如果您显式提供NULL
,则会报错。GENERATED BY DEFAULT ON NULL AS IDENTITY
:默认或在NULL时自动生成。这是最灵活的策略。当INSERT
语句中未指定该列,或为该列显式提供了NULL
值时,系统都会自动为其生成值。这种方式在数据迁移或需要混合插入自定义ID和自动生成ID的场景中非常有用。
前提条件
您的PolarDB PostgreSQL版(兼容Oracle)集群的修订版本需为2.0.14.17.36.0及以上
优势
简化开发:自动生成列将ID生成的逻辑封装在表定义中,使
INSERT
语句更简洁,无需关心序列的调用。保证唯一性:由数据库保证生成的序列值是唯一的,避免了应用层生成ID可能导致的冲突。
灵活性强:
GENERATED BY DEFAULT ON NULL
策略兼顾了自动生成和手动指定的双重需求,特别适合数据导入和历史数据迁移等复杂场景。
选型建议
为了帮助您更好地选择,下表对比了不同序列生成方式的特性:
生成方式 | 手动指定值 | 显式插入NULL | 灵活性 | 数据迁移友好度 | 推荐场景 |
传统序列 ( | 支持 | 支持(列需允许NULL) | 最高 | 高 | 与多表共享序列。 |
强制生成 ( | 不支持 | 不适用 | 最低 | 低 | 严格自增,禁止干预。 |
默认生成 ( | 支持 | 不支持(报错) | 中 | 中 | 允许覆盖ID,但不允许NULL。 |
默认或NULL时生成 ( | 支持 | 支持(触发自动生成) | 高 | 最高 | 新应用、数据迁移、混合ID场景。 |
最佳实践
对于新开发的应用,推荐使用
GENERATED BY DEFAULT ON NULL AS IDENTITY
,因为它提供了最大的灵活性。在进行数据迁移或需要导入包含存量ID的数据时,
GENERATED BY DEFAULT ON NULL
是理想选择。如果业务要求ID必须由数据库严格控制且绝不允许手动干预,请使用
GENERATED ALWAYS AS IDENTITY
。在高并发插入场景下,为
IDENTITY
列或传统序列设置一个合理的CACHE
值(如CACHE 20
或更高)可以显著提升性能。
使用传统序列(Sequence)
这种方式下,序列和表是两个独立的对象。
创建序列
首先,使用
CREATE SEQUENCE
命令创建一个序列对象,并可以自定义其起始值、步长、最大/小值等属性。CREATE SEQUENCE emp_seq START WITH 1000 -- 起始值为1000 INCREMENT BY 1 -- 每次递增1 MAXVALUE 9999 -- 最大值为9999 NOCYCLE -- 不循环 CACHE 20; -- 缓存20个值以提高性能
参数说明
START WITH
:起始值。INCREMENT BY
:增量步长。MAXVALUE/NOMAXVALUE
:最大值。MINVALUE/NOMINVALUE
:最小值。CYCLE/NOCYCLE
:是否循环。CACHE/NOCACHE
:缓存数量。ORDER/NOORDER
:是否保证顺序。
在
INSERT
语句中使用序列在向表中插入数据时,通过调用序列的
NEXTVAL
伪列来获取新的ID。-- 创建一个使用序列的表 CREATE TABLE employees ( emp_id NUMBER(10) PRIMARY KEY, emp_name VARCHAR2(50) NOT NULL ); -- 插入数据时调用emp_seq.NEXTVAL INSERT INTO employees (emp_id, emp_name) VALUES (emp_seq.NEXTVAL, 'John Smith'); INSERT INTO employees (emp_id, emp_name) VALUES (emp_seq.NEXTVAL, 'Jane Doe'); -- 查询结果 SELECT * FROM employees; -- emp_id | emp_name -- --------+------------ -- 1000 | John Smith -- 1001 | Jane Doe
查询结果
SELECT * FROM employees;
返回结果:
emp_id | emp_name --------+------------ 1000 | John Smith 1001 | Jane Doe (2 rows)
管理序列
您可以查询
user_sequences
视图来查看序列的当前状态,或使用ALTER SEQUENCE
修改其属性。-- 查看序列的当前值(需要先调用一次NEXTVAL) SELECT emp_seq.CURRVAL FROM dual; -- 修改序列的步长和缓存 ALTER SEQUENCE emp_seq INCREMENT BY 2 CACHE 50;
使用自动生成列(Identity Column)
这种方式将序列与表列直接绑定,更为现代化和便捷。
强制生成(GENERATED ALWAYS)
适用于所有ID都应由数据库严格控制的场景。
创建表:创建一个带有强制自动生成列的表,并插入数据。
-- 创建一个带有强制自动生成列的表 CREATE TABLE products ( product_id NUMBER GENERATED ALWAYS AS IDENTITY ( START WITH 100 INCREMENT BY 10 ), product_name VARCHAR2(100) NOT NULL ); -- 插入数据时,无需也不能指定product_id列 INSERT INTO products (product_name) VALUES ('Laptop'); INSERT INTO products (product_name) VALUES ('Mouse');
尝试显式插入ID。
INSERT INTO products (product_id, product_name) VALUES (999, 'Keyboard');
返回结果:报错。
ERROR: cannot insert a non-DEFAULT value into column "product_id" DETAIL: Column "product_id" is an identity column defined as GENERATED ALWAYS.
查询结果
SELECT * FROM products ORDER BY product_id;
返回结果:
product_id | product_name ------------+-------------- 100 | Laptop 110 | Mouse (2 rows)
默认生成(GENERATED BY DEFAULT)
此方式允许您在需要时覆盖自动生成的ID,但与ON NULL
不同,它不允许显式插入NULL
。
创建表。
CREATE TABLE transactions ( trans_id NUMBER GENERATED BY DEFAULT AS IDENTITY, amount NUMBER(10,2) );
插入方式1:不指定ID,自动生成。
INSERT INTO transactions (amount) VALUES (500.00);
插入方式2:显式插入一个自定义。
INSERT INTO transactions (trans_id, amount) VALUES (9999, 200.00);
插入方式3:显式插入NULL将会失败。
INSERT INTO transactions (trans_id, amount) VALUES (NULL, 150.00);
返回结果:报错。
ERROR: null value in column "trans_id" of relation "transactions" violates not-null constraint DETAIL: Failing row contains (null, 150).
查询结果
SELECT * FROM transactions ORDER BY trans_id;
返回结果:
trans_id | amount ----------+-------- 1 | 500 9999 | 200 (2 rows)
默认或在NULL时生成(GENERATED BY DEFAULT ON NULL)
这是最灵活的方式,推荐在新应用和数据迁移场景中使用。
创建表:创建一个带有灵活自动生成列的表。
-- 创建一个带有灵活自动生成列的表 CREATE TABLE customers ( customer_id NUMBER GENERATED BY DEFAULT ON NULL AS IDENTITY, customer_name VARCHAR2(100) NOT NULL );
插入方式1:不指定ID,由数据库自动生成。
INSERT INTO customers (customer_name) VALUES ('John Doe');
插入方式2:显式插入一个自定义ID(例如,迁移历史数据)。
INSERT INTO customers (customer_id, customer_name) VALUES (5000, 'Jane Smith');
插入方式3:显式插入NULL,由数据库自动生成。
INSERT INTO customers (customer_id, customer_name) VALUES (NULL, 'Bob Wilson');
查询结果
SELECT * FROM customers ORDER BY customer_id;
返回结果:
customer_id | customer_name -------------+--------------- 1 | John Doe 2 | Bob Wilson 5000 | Jane Smith (3 rows)
序列管理与维护
无论是传统序列还是与自动生成列关联的内部序列,您都可以对其进行管理和维护。
查看序列信息
您可以查询user_sequences
视图来监控所有用户自定义序列的状态。
SELECT sequence_name, min_value, max_value, increment_by,
last_number, cache_size, cycle_flag
FROM user_sequences;
修改序列属性
使用ALTER SEQUENCE
命令可以修改序列的步长、最大值、缓存大小等属性。
-- 将emp_seq序列的步长改为2,最大值改为99999,缓存改为50
ALTER SEQUENCE emp_seq
START WITH 1005
INCREMENT BY 2
MAXVALUE 99999
CACHE 50;
重置序列
PolarDB PostgreSQL版(兼容Oracle)不支持重置序列的命令。标准的做法是删除并重新创建序列。
-- 删除现有序列
DROP SEQUENCE emp_seq;
-- 重新创建序列,并设置新的起始值
CREATE SEQUENCE emp_seq START WITH 5000 INCREMENT BY 1;
实际应用场景
在用户注册场景中,GENERATED BY DEFAULT ON NULL
提供了极大的便利。它既能为新注册用户自动生成ID,也允许在数据迁移或批量导入时指定历史ID。
创建用户账户表。
CREATE TABLE user_accounts ( user_id NUMBER GENERATED BY DEFAULT ON NULL AS IDENTITY ( START WITH 100000 CACHE 100 ), username VARCHAR2(50) UNIQUE NOT NULL, email VARCHAR2(100) UNIQUE NOT NULL, created_date DATE DEFAULT SYSDATE );
场景1:新用户正常注册,自动生成ID。
INSERT INTO user_accounts (username, email) VALUES ('john_doe', 'john@example.com');
场景2:从旧系统迁移数据,指定历史ID。
INSERT INTO user_accounts (user_id, username, email, created_date) VALUES (99999, 'legacy_user', 'leg***@system.com', DATE '2020-01-01');
场景3:批量导入,混合使用自动生成和指定ID。
INSERT ALL INTO user_accounts (username, email) VALUES ('alice', 'ali**@test.com') INTO user_accounts (user_id, username, email) VALUES (88888, 'system_acct', 'sys***@app.com') SELECT * FROM dual;
查询最终结果。
SELECT * FROM user_accounts ORDER BY user_id;
返回结果:
user_id | username | email | created_date ---------+-------------+-------------------+--------------------- 88888 | system_acct | sys***@app.com | 2025-09-17 10:21:38 99999 | legacy_user | leg***@system.com | 2020-01-01 00:00:00 100000 | john_doe | john@example.com | 2025-09-17 10:21:38 100001 | alice | ali**@test.com | 2025-09-17 10:21:38 (4 rows)