22 标识列与序列
你很少需要手动选择主键值。PostgreSQL 提供两种自动生成主键值的方式:现代的 SQL 标准语法 GENERATED ... AS IDENTITY,以及较旧的 serial / bigserial 简写。两者都由一个序列驱动——序列是一个独立对象,负责发放递增的数字。新代码推荐使用 IDENTITY;旧代码库中仍然常见 serial。
本章要点
GENERATED ALWAYS AS IDENTITY语法- 与较旧的
serial简写的区别 - 序列是什么,以及如何检查序列
- 为什么生成的 ID 可能出现间断
- 批量装载后重置序列
示例
GENERATED ALWAYS AS IDENTITY 告诉 PostgreSQL,该列的值来自底层序列,INSERT 不能覆盖这个值(除非使用 OVERRIDING SYSTEM VALUE)。GENERATED BY DEFAULT AS IDENTITY 是限制更少的变体——如果 INSERT 提供了值,就使用该值;否则由序列提供。若要防止应用程序代码意外注入 ID,请使用 ALWAYS;如果需要为迁移和测试保留灵活性,则使用 BY DEFAULT。
较旧的 serial(integer + 序列)和 bigserial(bigint + 序列)是 integer NOT NULL DEFAULT nextval('seq') 的语法糖。它们仍然可用,但 IDENTITY 是 SQL 标准,与 pg_dump 配合得更好,而且对序列所有权的跟踪更清晰。
序列独立于事务:nextval() 不会回滚。这意味着,如果某个事务插入一行后又回滚,该 ID 就被消耗掉了——下一行会得到它之后的值。ID 可能出现间断,应用程序绝不能假设它们是连续的。
运行示例
运行方式:
常见陷阱
- 使用另一个连接中的
currval()读取“我刚插入的最后一个 ID”——这样行不通。序列是共享对象;只有currval()记住的值按会话分别保存。请改用RETURNING id。 - 批量装载并提供显式 ID 后,序列仍停留在旧值,因此下一个自动生成的 ID 会发生冲突。请重置它:
SELECT setval('t_id_seq', (SELECT max(id) FROM t));。 - 不要指望 ID 连续或反映插入顺序,尤其是在并发场景下。如果需要排序,请使用
created_at, id。 - 在数据量很大的表上为标识列使用
integer(32 位)——你可能耗尽取值范围。请使用bigint。
从 MySQL 的 AUTO_INCREMENT 迁移时,最安全的直接等价写法是 bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY。它接受显式 ID(适合迁移),否则就自动生成。
在 messages_example 中执行两次 INSERT,然后在一个事务中对其中一次执行 ROLLBACK(BEGIN; INSERT ...; ROLLBACK;),再执行一次 INSERT——你应该能看到 ID 出现间断。然后使用 setval() 重置序列,观察下一次 INSERT 从那里继续。
下载
SQL 文件:identity-and-sequences.sql · 全部示例文件 · 准备工作