首页 / PostgreSQL 入门教程 / 序列(SEQUENCE)与生成列

PostgreSQL 入门教程

序列(SEQUENCE)与生成列

本教程共 50 篇 · 第 43 篇 · 更新于 2026-07-31 · 约 7 分钟阅读

PostgreSQLPostgreSQL 入门教程序列SEQUENCE生成列nextval

43. 序列(SEQUENCE)与生成列

本节目标:理解序列如何生成唯一数字,掌握 CREATE SEQUENCE 与生成列(GENERATED)的用法。

什么是序列

序列(SEQUENCE)是数据库里专门产唯一整数号的对象。最常见用途就是给主键发号。

其实你用 GENERATED AS IDENTITY 建主键时,PostgreSQL 背后就是自动建了一个序列。本章看看怎么自己掌控它,适合需要跨表统一发号、或自定义起始值的场景。

创建序列

postgres=# CREATE SEQUENCE order_no_seq
postgres=#   START WITH 1000
postgres=#   INCREMENT BY 1;

可选参数:

  • INCREMENT BY n:步长,负数就是倒着生成。
  • MINVALUE / MAXVALUE:上下限。
  • CYCLE / NO CYCLE:到头了是否从头再来(默认不循环)。
  • CACHE n:预取 n 个到内存,提升并发性能,但崩溃可能丢失一段号。
  • OWNED BY 表.列:列删了序列跟着删。
postgres=# CREATE SEQUENCE order_no_seq
postgres=#   START WITH 1000
postgres=#   INCREMENT BY 1
postgres=#   CACHE 10
postgres=#   OWNED BY orders.id;

取号:nextval 与 currval

nextval 取下一个号,并且一定自增,多会话并发也安全:

postgres=# SELECT nextval('order_no_seq');  -- 1000
postgres=# SELECT nextval('order_no_seq');  -- 1001

currval 拿”本次会话上一次 nextval 取到的值”:

postgres=# SELECT currval('order_no_seq');  -- 1001

还有 lastval(),返回本次会话里”任意序列”最近一次 nextval 的值,不用写序列名:

postgres=# SELECT lastval();
Warning

没在本会话调用过 nextval 就直接用 currvallastval,会报错。它们是会话级别的,别的会话取到的值你看不到。

setval 可以手动把序列拨到某个值:

postgres=# SELECT setval('order_no_seq', 5000);  -- 下次 nextval 从 5001 起
postgres=# SELECT setval('order_no_seq', 5000, false);  -- 下次 nextval 就是 5000

setval 的第三个参数 is_called:true(默认)表示”已用过这个数”,下次从 5001 起;false 表示”下次就用 5000”。

Note

序列号不保证连续。事务回滚、ON CONFLICT 冲突,都会”浪费”号,留下空洞,这是正常现象。别拿序列号当”连续业务单号”去对账。

在表里用序列

手动把序列接到列上:

postgres=# CREATE TABLE orders (
postgres=#   id         INT DEFAULT nextval('order_no_seq') PRIMARY KEY,
postgres=#   user_id    INT,
postgres=#   amount     NUMERIC(10,2)
postgres=# );

IDENTITY 与序列的关系

第 15 章讲过,主键推荐用 GENERATED ALWAYS AS IDENTITY,它底层就是序列,但由系统托管:

postgres=# CREATE TABLE orders (
postgres=#   id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
postgres=#   ...
postgres=# );

想重置它的起始值,用 ALTER TABLE ... RESTART

postgres=# ALTER TABLE orders ALTER COLUMN id RESTART WITH 1000;
Tip

想给主键自动发号,优先用 GENERATED AS IDENTITY,比手写序列更省心(删表时序列自动清理)。序列适合需要跨表统一发号、或自定义起始值 / 步长的场景。

生成列(GENERATED)

生成列(Generated Column)的值由表达式自动算出来,插数据时你不能手动指定它。分两种:

  • GENERATED ALWAYS AS (表达式) STORED:结果真实存到磁盘。
  • GENERATED ALWAYS AS (表达式) VIRTUAL:不存,查询时现算。PostgreSQL 18 起正式支持 VIRTUAL,而且省略关键字时默认就是 VIRTUAL。
postgres=# CREATE TABLE order_items (
postgres=#   id     INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
postgres=#   qty    INT,
postgres=#   price  NUMERIC(10,2),
postgres=#   total  NUMERIC(12,2) GENERATED ALWAYS AS (qty * price) STORED
postgres=# );

postgres=# INSERT INTO order_items (qty, price) VALUES (3, 9.5);
postgres=# SELECT * FROM order_items;  -- total 自动是 28.50

生成列可以引用其它普通列,甚至引用另一个生成列:

postgres=# CREATE TABLE order_items2 (
postgres=#   id      INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
postgres=#   qty     INT,
postgres=#   price   NUMERIC(10,2),
postgres=#   subtotal NUMERIC(12,2) GENERATED ALWAYS AS (qty * price) STORED,
postgres=#   tax      NUMERIC(12,2) GENERATED ALWAYS AS (subtotal * 0.13) STORED
postgres=# );
Note

生成列和视图不同:生成列是真实存着的列,跟着行一起读写;它也不能在 INSERT 里赋值,强行写会报错。它可以建索引,适合常按派生值筛选的场景。

生成列的限制

  1. 不能在 INSERT / UPDATE 里给生成列赋值(除非用 DEFAULT 关键字)。
  2. 生成列不能写自己的 DEFAULT。
  3. 生成列的表达式不能含易变函数(如 random()now()),也不能引用其它表的列。
postgres=# INSERT INTO order_items (qty, price, total) VALUES (3, 9.5, 30);
ERROR:  cannot insert into column "total"
DETAIL: Column "total" is a generated column.
Tip

想手动覆盖一次生成列的值,可以写 INSERT ... (qty, price, total) VALUES (3, 9.5, DEFAULT),让数据库照常计算,而不是报错。

序列的边界与循环

序列底层是 bigint,理论上限很大(约 922 亿亿亿)。如果 NO CYCLE(默认)且用到底,下一个 nextval 会报错。想让它到头后从头来,建时加 CYCLE

postgres=# CREATE SEQUENCE seq_cycle START WITH 1 INCREMENT BY 1 MAXVALUE 3 CYCLE;
postgres=# SELECT nextval('seq_cycle');  -- 1, 2, 3, 然后又回到 1
Warning

CYCLE 有坑:它会让”已经用过”的号重新出现,如果这号被当成唯一键就会冲突。一般只在对唯一性没要求的场景(比如循环计数器)才用。

复制表时序列的坑

CREATE TABLE new AS SELECT * FROM old 复制数据时,自增列会原样把当前值拷过去,但不会复制背后的序列,新表插入可能主键冲突。想连结构带序列复制,用 LIKE ... INCLUDING ALL

postgres=# CREATE TABLE orders_bak (LIKE orders INCLUDING ALL);

如果只用 LIKE orders,IDENTITY 属性会保留但新序列从 1 开始,老数据可能占了 1 号,插入就撞。复制后记得 ALTER ... RESTART WITH 调整起点。

生成列上也能建索引

生成列是真实存在的列,当然能建索引,适合常按派生值筛选、排序的场景:

postgres=# CREATE INDEX idx_order_items_total ON order_items (total);
postgres=# SELECT * FROM order_items WHERE total > 100 ORDER BY total;

三种派生数据方式怎么选

  • 视图:不存数据,实时,适合封装查询、控权限,但慢。
  • 生成列:存数据,随行自动算,快,但只能依赖本行其它列。
  • 触发器计算字段:最灵活,能跨表、跨行算,但写起来最重、拖慢写入。
Tip

如果只是本行内的简单计算(如 qty * price),优先用生成列,最省心;需要跨行汇总才上触发器。

序列 vs IDENTITY 到底选谁

一句话:给主键发号,优先 GENERATED AS IDENTITY,系统帮你管序列、删表时自动清理,最省心。需要”跨多张表共用一套号""自定义起始值或循环""手动 setval 跳号”这类高级玩法,才自己建 SEQUENCE。多数业务用 IDENTITY 就够了,别为了炫技手搓序列。

生成列的适用边界

生成列适合”本行内、确定性的派生值”,比如金额小计、全名拼接。它不能跨行汇总(那是视图或触发器的事),也不能引用其他表。如果派生规则会失败(比如除零),生成列会直接拒绝写入——这其实是好事,保证了数据干净。

Note

GENERATED ALWAYS AS IDENTITY 不允许你手动插入 id,写 INSERT 时硬塞值会报错。如果你确实需要临时手填(比如迁移老数据),改用 GENERATED BY DEFAULT AS IDENTITY:它会优先用你给的值,没给时再由序列发号。两者底层都是序列,差别只在「是否允许手动指定」,迁移场景用 BY DEFAULT 最方便。但要注意,BY DEFAULT 下如果你手填的值和序列已发出的号撞了,就会主键冲突,所以手填前最好先 RESTART WITH 让序列跳到更大的起点。

常见误区

  1. 把序列号当连续业务号对账,结果因为空洞对不上。
  2. 往生成列里 INSERT 值,直接报错。
  3. VOLATILE 函数写生成列表达式,PG 不允许。
  4. 给生成列再写 DEFAULT,冲突报错——生成列本身就包含计算规则。