在PostgreSQL中,实现主键自增并不像MySQL那样直接使用AUTO_INCREMENT关键字,而是依托序列(sequence)对象与特定的数据类型或约束来完成。序列是数据库中一种独立的对象,负责按设定规则生成连续的数值,常被用作表字段的默认值来源。理解这一点,是掌握PostgreSQL主键自增配置的核心。

一、使用SERIAL与BIGSERIAL伪类型
SERIAL是PostgreSQL提供的一种伪类型,并非真正的独立类型,它在底层会自动创建一个序列,并将该序列的下一个值作为字段的默认值。当我们声明某字段为SERIAL时,数据库相当于执行了创建序列、设置默认值、赋予所有权三步操作。BIGSERIAL则对应八字节整型,适合数据量极大的表。
下面是一个使用SERIAL创建自增主键的示例:
CREATE TABLE users (
id SERIAL PRIMARY KEY,
username VARCHAR(50) NOT NULL,
email VARCHAR(100)
);
-- 插入数据时无需指定id,数据库自动填充
INSERT INTO users (username, email) VALUES ('zhangsan', 'zhangsan@ipipp.com');
INSERT INTO users (username, email) VALUES ('lisi', 'lisi@ipipp.com');
-- 查看当前序列值
SELECT currval('users_id_seq');
这种方式的优点是语法简洁,建表时一行即可搞定自增逻辑。但需要注意,SERIAL只是语法糖,它创建的序列与字段之间是所有权关系而非强约束。如果手动修改了序列的起始值或删除了序列,可能导致插入冲突。此外,SERIAL并不阻止用户显式插入数值,若插入的值大于当前序列值,后续自动生成的值可能与之冲突。
从权限角度看,SERIAL自动创建的序列归属于表的所有者,其他被授权用户插入数据时可以正常使用默认值,但在执行重置序列等操作时仍需序列权限。在团队开发中,建议通过迁移脚本统一管理序列,避免不同环境序列状态不一致。
二、使用IDENTITY列(GENERATED AS IDENTITY)
从PostgreSQL 10开始,引入了SQL标准中的IDENTITY列,通过GENERATED ALWAYS AS IDENTITY或GENERATED BY DEFAULT AS IDENTITY来定义。相比SERIAL,它更加规范,且由系统目录直接记录依赖关系,属于表结构的一部分。
以下示例展示如何创建IDENTITY自增主键:
CREATE TABLE orders (
id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
order_no VARCHAR(32) NOT NULL,
amount NUMERIC(10,2)
);
-- 正常插入,由系统生成id
INSERT INTO orders (order_no, amount) VALUES ('NO202401', 99.50);
-- 若想显式插入,需先覆盖系统生成行为
INSERT INTO orders (id, order_no, amount)
OVERRIDING SYSTEM VALUE
VALUES (100, 'NO202402', 20.00);
GENERATED ALWAYS模式下,数据库默认禁止用户显式指定id,必须通过OVERRIDING SYSTEM VALUE才能覆盖,这有效防止了人为插入导致的序列脱节。而GENERATED BY DEFAULT则允许显式插入,行为更接近SERIAL。IDENTITY列的序列信息存储在pg_sequences系统视图中,与表绑定更紧密,在表删除时序列也会被自动清理。
在备份与恢复场景中,IDENTITY列比SERIAL更可靠,因为逻辑备份工具能准确识别其依赖并重建。对于新项目,官方推荐优先使用IDENTITY而非SERIAL,以便获得更好的标准兼容性与维护性。
三、手动创建序列并绑定默认值
当多个表需要共享同一套编号,或需要精细控制序列参数(如缓存、循环)时,可以抛开伪类型,手动创建序列对象,再将其设为字段的默认值。这种方式最为灵活。
示例代码如下:
-- 创建序列,设置起始值与缓存
CREATE SEQUENCE global_id_seq
START WITH 1
INCREMENT BY 1
CACHE 20;
-- 建表并引用序列默认值
CREATE TABLE products (
id INT PRIMARY KEY DEFAULT nextval('global_id_seq'),
name TEXT
);
CREATE TABLE invoices (
id INT PRIMARY KEY DEFAULT nextval('global_id_seq'),
total NUMERIC
);
-- 插入测试
INSERT INTO products (name) VALUES ('键盘');
INSERT INTO invoices (total) VALUES (199.00);
手动序列方案让跨表唯一序号成为可能,例如业务要求订单与退款单使用同一发号器。通过nextval函数获取下一个值,还能在事务中提前占号。不过,由于序列是非事务性的,回滚不会归还已取走的号,因此可能出现号段空洞,这是正常现象而非错误。
使用手动序列时,要特别注意权限分配。如果应用程序账号没有序列的USAGE权限,执行带默认值的插入会报错。同时,在复制或切库时,务必连同序列定义一起迁移,否则新环境会因找不到序列而写入失败。
四、常见问题与维护操作
无论采用哪种方式,实际运维中都可能遇到主键冲突或序列不同步。例如,手工导入了一批历史数据后,序列仍停留在旧值,下次自动插入就会报重复主键错误。此时需用setval函数将序列同步到当前最大id之后。
-- 将users表的序列调整到当前最大id
SELECT setval(
'users_id_seq',
(SELECT MAX(id) FROM users)
);
-- 查看序列详细状态
SELECT * FROM pg_sequences WHERE sequencename = 'users_id_seq';
另一个易错点是在使用SERIAL时,通过ORM框架自动建表后,又手动执行了TRUNCATE并指定RESTART IDENTITY以外的选项,导致序列未重置。建议在测试环境清空表时使用TRUNCATE TABLE users RESTART IDENTITY,确保序列与数据同步归零。
综合来看,PostgreSQL的主键自增并非单一功能,而是序列机制的不同封装。新项目优先选IDENTITY,旧项目兼容可用SERIAL,特殊发号需求用手动序列。掌握其原理与维护命令,才能让自增主键稳定服务于业务系统。
postgresql主键自增serial修改时间:2026-08-02 17:12:37