Skip to content

注释(COMMENT)与其他对象管理 ​

表结构写完只是"能用",加上注释(Comment)才算"好维护"。本节讲 COMMENT ON 的全套用法与查询方式,并补齐 DDL 中另一类高频对象——序列(Sequence):它的创建、取值、与自增列的关系,以及为什么现代 PostgreSQL 项目应该优先用 IDENTITY 而不是 serial。

前置知识

阅读本节前,建议先了解:临时表与非日志表(TEMPORARY / UNLOGGED)

基础概念 ​

COMMENT ON 可以为几乎所有数据库对象挂一段文字说明,存储在系统目录 pg_description 中。它不是"代码注释"——注释会持久化,能被工具读取,也能通过 \d+、information_schema、ORM 反向工程看到。

对象类型语法存放位置
表COMMENT ON TABLE t IS '...'pg_description(classoid = pg_class)
列COMMENT ON COLUMN t.c IS '...'pg_description(objsubid = 列号)
约束COMMENT ON CONSTRAINT c ON t IS '...'pg_description(classoid = pg_constraint)
索引COMMENT ON INDEX i IS '...'pg_description(classoid = pg_class)
视图 / 物化视图COMMENT ON VIEW v IS '...'pg_description
模式COMMENT ON SCHEMA s IS '...'pg_description(classoid = pg_namespace)
数据库COMMENT ON DATABASE d IS '...'pg_shdescription(共享目录)
序列COMMENT ON SEQUENCE s IS '...'pg_description
函数COMMENT ON FUNCTION f(int) IS '...'pg_description(需带参数类型)
类型 / 域COMMENT ON TYPE t IS '...'pg_description
触发器COMMENT ON TRIGGER tr ON t IS '...'pg_description
扩展COMMENT ON EXTENSION e IS '...'pg_description
权限/角色COMMENT ON ROLE r IS '...'pg_shdescription

几个关键概念:

  • pg_description:存放"库内对象"的注释,主键为 (objoid, classoid, objsubid)。objsubid = 0 表示对象本身,> 0 表示它的某一列。
  • pg_shdescription:存放"跨库共享对象"(数据库、表空间、角色)的注释,主键为 (objoid, classoid)。
  • obj_description() / col_description() / shobj_description():三个内置函数,分别用于读取对象注释、列注释、共享对象注释,比自己 join 目录表方便得多。
  • IS NULL 即删除:COMMENT ON ... IS NULL 会删除已有注释,而不是写入空串。
  • 序列(Sequence):一类特殊的单行表,专门生成单调递增整数。它不属于事务——回滚不会退还已取走的号。
  • serial 不是类型:它是 integer + 序列 + DEFAULT 表达式 + OWNED BY 的语法糖。GENERATED ... AS IDENTITY 才是 SQL 标准写法。

语法与用法 ​

text
-- 注释:写入
COMMENT ON { TABLE | COLUMN | CONSTRAINT ... ON ... | INDEX | VIEW | MATERIALIZED VIEW |
             SCHEMA | DATABASE | SEQUENCE | FUNCTION | TYPE | DOMAIN | TRIGGER ... |
             EXTENSION | ROLE | TABLESPACE } object_name IS 'text';

-- 注释:删除(写成 NULL)
COMMENT ON TABLE t IS NULL;

-- 序列:创建
CREATE [ { TEMPORARY | TEMP } | UNLOGGED ] SEQUENCE [ IF NOT EXISTS ] name
    [ AS data_type ]                                          -- PostgreSQL 10+
    [ INCREMENT [ BY ] increment ]
    [ MINVALUE minvalue | NO MINVALUE ] [ MAXVALUE maxvalue | NO MAXVALUE ]
    [ START [ WITH ] start ] [ CACHE cache ] [ [ NO ] CYCLE ]
    [ OWNED BY { table_name.column_name | NONE } ]

-- 序列:取值与操作
nextval('seq')        -- 取下一个值(破坏性,会推进序列)
currval('seq')        -- 取本会话当前值(未取过则报错)
lastval()             -- 取本会话最后一个 nextval 的值
setval('seq', n [, is_called])   -- 直接设定值

最简可运行示例:

sql
-- 给表与列加注释
CREATE TABLE products (
  id    bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  sku   text   NOT NULL,
  price numeric(10,2) NOT NULL
);

COMMENT ON TABLE  products       IS '商品主表';
COMMENT ON COLUMN products.sku   IS '商品编码,全局唯一';
COMMENT ON COLUMN products.price IS '售价(含税),单位:元';
text
CREATE TABLE
COMMENT
COMMENT
COMMENT

详细说明 ​

COMMENT ON 的完整用法 ​

注释支持几乎所有对象类型,写法统一为 COMMENT ON <对象类型> <对象标识> IS '<内容>':

sql
-- 表、列、约束、索引
COMMENT ON TABLE  orders           IS '订单主表,单条记录为一笔订单';
COMMENT ON COLUMN orders.status    IS '订单状态:pending/paid/shipped/cancelled';
COMMENT ON CONSTRAINT ck_orders_status ON orders IS '状态枚举校验';
COMMENT ON INDEX  idx_orders_user_id   IS '加速按用户查订单';

-- 视图、物化视图
COMMENT ON VIEW v_active_users IS '活跃用户视图,仅含 status=active';

-- 模式与数据库
COMMENT ON SCHEMA public IS '默认模式,业务表都放这里';
COMMENT ON DATABASE demo  IS '演示库,2026-Q3 建立,仅供开发环境使用';

-- 函数必须带参数类型(重载函数靠参数签名区分)
COMMENT ON FUNCTION calc_tax(numeric, text) IS '计算含税金额,参数为(金额, 税率类别)';

-- 触发器
COMMENT ON TRIGGER trg_orders_audit ON orders IS '订单变更审计';

-- 扩展
COMMENT ON EXTENSION pg_stat_statements IS 'SQL 执行统计,用于性能分析';

注意 COMMENT ON DATABASE 存放于共享目录,因此它的注释对所有库可见,其他对象的注释只在本库内可见。

删除注释就是把它设为 NULL:

sql
COMMENT ON COLUMN orders.legacy_note IS NULL;
text
COMMENT

查询注释:三种方式 ​

方式一:psql 元命令 \d+(最直观)

sql
\d+ products
text
                                          Table "public.products"
   Column   |    Type     | Collation | Nullable |           Default            | Description
------------+-------------+-----------+----------+------------------------------+----------------------
 id         | bigint      |           | not null | generated always as identity |
 sku        | text        |           | not null |                              | 商品编码,全局唯一
 price      | numeric(10,2)|          | not null |                              | 售价(含税),单位:元
Indexes:
    "products_pkey" PRIMARY KEY, btree (id)

方式二:专用函数(推荐脚本使用)

sql
-- 表注释
SELECT obj_description('products'::regclass, 'pg_class') AS table_comment;

-- 列注释
SELECT col_description('products'::regclass, 2) AS sku_comment;

-- 数据库注释(共享目录,用 shobj_description)
SELECT shobj_description(oid, 'pg_database') AS db_comment
FROM pg_database WHERE datname = 'demo';
text
 table_comment
---------------
 商品主表
(1 row)

 sku_comment
------------------
 商品编码,全局唯一
(1 row)

   db_comment
-----------------------------------------------
 演示库,2026-Q3 建立,仅供开发环境使用
(1 row)

方式三:直接查目录表(可批量导出)

sql
-- 一次性导出一张表所有列的注释
SELECT
  a.attnum                AS col_no,
  a.attname               AS column_name,
  format_type(a.atttypid, a.atttypmod) AS data_type,
  d.description           AS comment
FROM pg_attribute a
LEFT JOIN pg_description d
       ON d.objoid = a.attrelid AND d.classoid = 'pg_class'::regclass AND d.objsubid = a.attnum
WHERE a.attrelid = 'products'::regclass
  AND a.attnum > 0
  AND NOT a.attisdropped
ORDER BY a.attnum;
text
 col_no | column_name |  data_type   |      comment
--------+-------------+--------------+--------------------
      1 | id          | bigint       |
      2 | sku         | text         | 商品编码,全局唯一
      3 | price       | numeric(10,2)| 售价(含税),单位:元
(3 rows)
sql
-- 查找"没有注释"的列:治理数据库文档的常用手段
SELECT c.relname AS table_name, a.attname AS column_name
FROM pg_class c
JOIN pg_attribute a ON a.attrelid = c.oid
LEFT JOIN pg_description d
       ON d.objoid = c.oid AND d.classoid = 'pg_class'::regclass AND d.objsubid = a.attnum
WHERE c.relnamespace = 'public'::regnamespace
  AND c.relkind = 'r'
  AND a.attnum > 0 AND NOT a.attisdropped
  AND d.description IS NULL
ORDER BY c.relname, a.attnum
LIMIT 20;

序列(SEQUENCE)基础 ​

序列是一个独立的数据库对象,用一条 nextval 原子地取下一个值。

sql
CREATE SEQUENCE order_no_seq
  AS bigint
  START WITH 1000
  INCREMENT BY 1
  NO MINVALUE
  NO MAXVALUE
  CACHE 1;
text
CREATE SEQUENCE
sql
-- 取值:每调用一次推进一次
SELECT nextval('order_no_seq') AS n1, nextval('order_no_seq') AS n2;
text
  n1  |  n2
------+------
 1000 | 1001
(2 rows)
sql
-- currval 取"本会话最后一次 nextval 的结果"
SELECT currval('order_no_seq');
text
 currval
---------
    1001
(1 row)
sql
-- 查看序列的当前状态(is_called 表示是否已取过值)
SELECT last_value, is_called FROM order_no_seq;
text
 last_value | is_called
------------+-----------
       1001 | t
(1 row)

序列不属于事务,这是它与 IDENTITY 都会被"跳号"的根源:

sql
BEGIN;
SELECT nextval('order_no_seq');   -- 取走 1002
ROLLBACK;                          -- 回滚
SELECT nextval('order_no_seq');   -- 是 1003,不是 1002
text
 nextval
---------
    1002
(1 row)

ROLLBACK

 nextval
---------
    1003
(1 row)

setval 用于手动设定,例如数据迁移后同步序列位置:

sql
-- 将序列设为 5000,下次 nextval 返回 5001
SELECT setval('order_no_seq', 5000);

-- 设为 5000,下次 nextval 返回 5000(is_called = false)
SELECT setval('order_no_seq', 5000, false);
text
 setval
--------
   5000
(1 row)

 setval
--------
   5000
(1 row)

serial vs IDENTITY vs SEQUENCE ​

三者都能实现自增,但实现方式与可靠性差异很大:

对比项serialGENERATED ... AS IDENTITY独立 SEQUENCE
是否 SQL 标准否(PostgreSQL 私有)是(SQL:2003)是
引入版本很早PostgreSQL 10+很早
依赖对象隐式创建序列(易被误删)序列由系统管理,随列存在独立对象,需自行管理
权限处理需额外授权序列自动跟随列权限需单独 GRANT USAGE
是否允许显式插入允许(可覆盖)ALWAYS 时禁止,需 OVERRIDING SYSTEM VALUE与列无关
序列名表名_列名_seq表名_列名_seq自定义
删除列时序列随列删除(有 OWNED BY)随列删除需手工删除
新项目建议不推荐推荐仅特殊场景
sql
-- 三种写法对照
CREATE TABLE t_serial (
  id serial PRIMARY KEY          -- 老写法
);

CREATE TABLE t_identity (
  id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY   -- 推荐
);

CREATE TABLE t_manual (
  id bigint NOT NULL DEFAULT nextval('t_manual_id_seq') PRIMARY KEY
);
CREATE SEQUENCE t_manual_id_seq OWNED BY t_manual.id;
sql
-- 将已有序列绑定到列,实现"随列删除"
ALTER SEQUENCE order_no_seq OWNED BY orders.id;
text
ALTER SEQUENCE

OWNED BY 的作用是:删除该列或该表时,序列被自动一起删除,避免留下孤儿序列。serial 与 IDENTITY 都自动带这个绑定。

序列的 cache 与性能 ​

sql
-- 查看 cache 设置:每次会话预分配的数量
CREATE SEQUENCE fast_seq CACHE 100;
text
CREATE SEQUENCE

CACHE n 让每个会话一次预留 n 个值,减少系统目录更新次数,在高并发插入时能显著提升吞吐。代价是崩溃或多会话场景下跳号更明显。默认 CACHE 1 最"省号",但并发写入时序列本身会成为热点。

sql
-- 查看序列的全部参数
SELECT seqrelid::regclass AS seq_name,
       seqtypid::regtype  AS data_type,
       seqstart, seqincrement, seqmax, seqmin, seqcache, seqcycle
FROM pg_sequence
WHERE seqrelid = 'order_no_seq'::regclass;
text
   seq_name   | data_type | seqstart | seqincrement |  seqmax   | seqmin | seqcache | seqcycle
--------------+-----------+----------+--------------+-----------+--------+----------+----------
 order_no_seq | bigint    |     1000 |            1 | 9223372036854775807 | 1 |        1 | f
(1 row)
sql
-- 查询所有序列的"水位",用于发现即将耗尽的自增 ID
SELECT
  schemaname || '.' || sequencename AS sequence,
  last_value,
  max_value,
  round(last_value::numeric / max_value * 100, 2) AS used_pct
FROM pg_sequences
WHERE max_value IS NOT NULL
ORDER BY used_pct DESC NULLS LAST
LIMIT 10;
sql
-- 找出所有与列失去绑定的"孤儿序列"
SELECT c.relname AS orphan_sequence
FROM pg_class c
LEFT JOIN pg_depend d ON d.objid = c.oid AND d.deptype IN ('a', 'i')
WHERE c.relkind = 'S' AND d.objid IS NULL;

实战示例 ​

场景:为一个新建的订单系统补全"可维护性"——① 给所有表与列加中英双语注释;② 创建一个对外可见的订单号序列;③ 把序列与列绑定;④ 事后导出完整数据字典。

第 1 步:建表

sql
DROP TABLE IF EXISTS orders CASCADE;
DROP TABLE IF EXISTS users  CASCADE;
DROP SEQUENCE IF EXISTS order_no_seq;

CREATE TABLE users (
  id         bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  email      text   NOT NULL,
  status     text   NOT NULL DEFAULT 'active',
  created_at timestamptz NOT NULL DEFAULT now(),
  CONSTRAINT uq_users_email   UNIQUE (email),
  CONSTRAINT ck_users_status  CHECK (status IN ('active', 'disabled', 'banned'))
);

CREATE TABLE orders (
  id          bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  user_id     bigint NOT NULL REFERENCES users (id),
  order_no    text   NOT NULL,
  amount      numeric(12,2) NOT NULL,
  status      text   NOT NULL DEFAULT 'pending',
  created_at  timestamptz NOT NULL DEFAULT now(),
  CONSTRAINT uq_orders_no     UNIQUE (order_no),
  CONSTRAINT ck_orders_amount CHECK (amount > 0)
);
text
CREATE TABLE
CREATE TABLE

第 2 步:加表与列注释

sql
-- 表级注释
COMMENT ON TABLE users  IS '用户主表(users):存放注册用户基本信息';
COMMENT ON TABLE orders IS '订单主表(orders):一笔订单一行,金额为含税总价';

-- 列级注释:写清楚取值范围与单位
COMMENT ON COLUMN users.email      IS '登录邮箱:全局唯一,NOT NULL';
COMMENT ON COLUMN users.status     IS '账号状态:active/disabled/banned';
COMMENT ON COLUMN users.created_at IS '注册时间(带时区)';

COMMENT ON COLUMN orders.user_id    IS '下单用户 ID,外键 → users.id';
COMMENT ON COLUMN orders.order_no   IS '业务订单号:对用户可见,全局唯一';
COMMENT ON COLUMN orders.amount     IS '订单总金额(含税),单位:元,必须 > 0';
COMMENT ON COLUMN orders.status     IS '订单状态:pending/paid/shipped/cancelled';
COMMENT ON COLUMN orders.created_at IS '下单时间(带时区)';

-- 约束注释
COMMENT ON CONSTRAINT ck_orders_amount ON orders IS '金额必须为正数';
COMMENT ON CONSTRAINT uq_orders_no     ON orders IS '订单号全局唯一';
text
COMMENT
COMMENT
COMMENT
COMMENT
COMMENT
COMMENT
COMMENT
COMMENT
COMMENT
COMMENT
COMMENT
COMMENT

第 3 步:用 \d+ 验证注释已生效

sql
\d+ orders
text
                                              Table "public.orders"
   Column   |           Type           | Nullable |           Default            |    Description
------------+--------------------------+----------+------------------------------+--------------------
 id         | bigint                   | not null | generated always as identity |
 user_id    | bigint                   | not null |                              | 下单用户 ID,外键 → users.id
 order_no   | text                     | not null |                              | 业务订单号:对用户可见,全局唯一
 amount     | numeric(12,2)            | not null |                              | 订单总金额(含税),单位:元,必须 > 0
 status     | text                     | not null | 'pending'::text              | 订单状态:pending/paid/shipped/cancelled
 created_at | timestamp with time zone | not null | now()                        | 下单时间(带时区)
Indexes:
    "orders_pkey" PRIMARY KEY, btree (id)
    "uq_orders_no" UNIQUE CONSTRAINT, btree (order_no)
Check constraints:
    "ck_orders_amount" CHECK (amount > 0::numeric)    -- 金额必须为正数
Foreign-key constraints:
    "orders_user_id_fkey" FOREIGN KEY (user_id) REFERENCES users(id)

第 4 步:创建业务订单号序列

sql
-- 订单号需要一个"业务可见"的流水号,与主键无关
CREATE SEQUENCE order_no_seq
  AS bigint
  START WITH 202600001
  INCREMENT BY 1
  NO CYCLE
  CACHE 20;
text
CREATE SEQUENCE
sql
-- 取号验证
SELECT nextval('order_no_seq') AS n1, nextval('order_no_seq') AS n2;
text
    n1     |    n2
-----------+-----------
 202600001 | 202600002
(2 rows)

第 5 步:把序列绑定到列

sql
-- 绑定后:删除 orders.id 或整张表时,序列自动一起删除
ALTER SEQUENCE order_no_seq OWNED BY orders.id;

-- 确认绑定关系已建立(deptype = 'a' 表示自动依赖)
SELECT d.deptype, d.objid::regclass AS sequence, d.refobjid::regclass AS table
FROM pg_depend d
WHERE d.objid = 'order_no_seq'::regclass AND d.deptype = 'a';
text
ALTER SEQUENCE

 deptype |   sequence   |  table
---------+--------------+---------
 a       | order_no_seq | orders
(1 row)

第 6 步:用序列生成业务订单号

sql
-- 用序列 + 日期前缀拼订单号
INSERT INTO users (email) VALUES ('alice@example.com'), ('bob@example.com');

INSERT INTO orders (user_id, order_no, amount, status)
SELECT (g % 2) + 1,
       'SO' || to_char(now(), 'YYYYMMDD') || lpad(g::text, 6, '0'),
       (random() * 900 + 100)::numeric(12,2),
       CASE WHEN g % 5 = 0 THEN 'pending' ELSE 'paid' END
FROM generate_series(1, 10) g;

SELECT order_no, amount, status FROM orders ORDER BY id LIMIT 5;
text
INSERT 0 2
INSERT 0 10

         order_no         | amount  | status
--------------------------+---------+---------
 SO20260924000001         |  512.34 | paid
 SO20260924000002         |  823.10 | paid
 SO20260924000003         |  190.55 | paid
 SO20260924000004         |  447.82 | paid
 SO20260924000005         |  671.09 | pending
(5 rows)

第 7 步:导出完整数据字典

sql
-- 一次性导出所有表与列的注释(可直接存成文档)
SELECT
  c.relname                                   AS table_name,
  obj_description(c.oid, 'pg_class')          AS table_comment,
  a.attnum                                    AS col_no,
  a.attname                                   AS column_name,
  format_type(a.atttypid, a.atttypmod)        AS data_type,
  a.attnotnull                                AS not_null,
  col_description(c.oid, a.attnum)            AS column_comment
FROM pg_class c
JOIN pg_attribute a ON a.attrelid = c.oid
WHERE c.relnamespace = 'public'::regnamespace
  AND c.relkind = 'r'
  AND c.relname IN ('users', 'orders')
  AND a.attnum > 0 AND NOT a.attisdropped
ORDER BY c.relname, a.attnum;
text
 table_name |              table_comment                | col_no | column_name |        data_type         | not_null |              column_comment
------------+-------------------------------------------+--------+-------------+--------------------------+----------+------------------------------------------
 orders     | 订单主表(orders):一笔订单一行,金额为含税总价 |      1 | id          | bigint                   | t        |
 orders     | ...                                        |      2 | user_id     | bigint                   | t        | 下单用户 ID,外键 → users.id
 orders     | ...                                        |      3 | order_no    | text                     | t        | 业务订单号:对用户可见,全局唯一
 orders     | ...                                        |      4 | amount      | numeric(12,2)            | t        | 订单总金额(含税),单位:元,必须 > 0
 orders     | ...                                        |      5 | status      | text                     | t        | 订单状态:pending/paid/shipped/cancelled
 orders     | ...                                        |      6 | created_at  | timestamp with time zone | t        | 下单时间(带时区)
 users      | 用户主表(users):存放注册用户基本信息       |      1 | id          | bigint                   | t        |
 users      | ...                                        |      2 | email       | text                     | t        | 登录邮箱:全局唯一,NOT NULL
(rows)

第 8 步:用注释反向查找字段

sql
-- 找出所有注释里提到"状态"的列:快速定位枚举字段
SELECT c.relname AS table_name, a.attname AS column_name, d.description
FROM pg_class c
JOIN pg_attribute a ON a.attrelid = c.oid
JOIN pg_description d
  ON d.objoid = c.oid AND d.classoid = 'pg_class'::regclass AND d.objsubid = a.attnum
WHERE c.relnamespace = 'public'::regnamespace
  AND c.relkind = 'r'
  AND d.description LIKE '%状态%'
ORDER BY c.relname, a.attnum;
text
 table_name | column_name |              description
------------+-------------+----------------------------------------
 orders     | status      | 订单状态:pending/paid/shipped/cancelled
 users      | status      | 账号状态:active/disabled/banned
(2 rows)

第 9 步:清理

sql
-- DROP TABLE 会自动带走 OWNED BY 的序列
DROP TABLE IF EXISTS orders, users CASCADE;

SELECT count(*) AS seq_left FROM pg_class WHERE relkind = 'S' AND relname = 'order_no_seq';
text
DROP TABLE

 seq_left
----------
        0
(1 row)

序列确实随表一起删掉了——这正是 OWNED BY 的价值。

注意事项 ​

  • COMMENT ON 不校验内容,也不影响执行。 但注释会被 pg_dump 导出,写错信息会长期误导使用者。
sql
-- 注释与真实行为不符,比没有注释更糟
COMMENT ON COLUMN orders.status IS '状态:只有两种值';
  • 修改注释是覆盖式写入,没有"追记"机制。
sql
COMMENT ON TABLE orders IS '订单主表';               -- 只剩这一句
COMMENT ON TABLE orders IS '订单主表(含退款)';      -- 完全替换,不是追加
  • COMMENT ON 需要对象的所有者权限。 非所有者执行会报错。
sql
-- 错误!
COMMENT ON TABLE someone_elses_table IS '...';
text
ERROR:  must be owner of table someone_elses_table
  • 列注释用列序号(objsubid)关联,删列后序号会错位。 直接查 pg_description 时必须关联 pg_attribute.attnum,而不是硬编码列号。
sql
-- 危险!硬编码列号,删列后会读错注释
SELECT description FROM pg_description WHERE objsubid = 3;

-- 正确:用 col_description + 列名
SELECT col_description('orders'::regclass, attnum)
FROM pg_attribute WHERE attrelid = 'orders'::regclass AND attname = 'amount';
  • 函数注释必须带完整参数类型,否则报"函数不存在"。
sql
-- 错误!function calc_tax(numeric) does not exist
COMMENT ON FUNCTION calc_tax IS '计算税额';

-- 正确
COMMENT ON FUNCTION calc_tax(numeric, text) IS '计算税额';
  • COMMENT ON DATABASE 存于共享目录,库被删后注释也一起消失,与其他对象的可见性不同。

  • 序列的号不会被回滚退还。 这是"跳号"的根源,业务上不应假设订单号连续。

sql
BEGIN;
SELECT nextval('order_no_seq');
ROLLBACK;
-- 这个号永久丢失
  • currval 在未调用 nextval 的会话里会报错。
sql
-- 错误!currval of sequence "order_no_seq" is not yet defined in this session
SELECT currval('order_no_seq');
  • serial 创建的序列是独立对象,可能被误删。 删掉序列后插入会立刻失败。
sql
-- 危险!业务直接挂掉
DROP SEQUENCE users_id_seq;
INSERT INTO users (email) VALUES ('x@example.com');
text
ERROR:  relation "users_id_seq" does not exist

新项目用 IDENTITY 可避免这类问题。

  • CACHE 越大跳号越明显。 高并发场景下 CACHE 100 可能一次跳几十个号。
sql
-- 对"号必须连续"的业务(如发票号),不能用数据库序列
CREATE SEQUENCE invoice_seq CACHE 1 NO CYCLE;
  • 序列耗尽会报错而非回绕(除非 CYCLE)。bigint 序列基本不会耗尽,但 smallint/integer 有可能。
sql
-- 监控序列水位,避免生产事故
SELECT sequencename, last_value, max_value FROM pg_sequences
WHERE sequencename = 'order_no_seq';
  • 序列不是事务安全的"无间隙"生成器,也不提供跨行回滚语义。需要严格连续编号时必须用表锁自行实现。

  • setval 需要 UPDATE 权限(PostgreSQL 8.1+),不只是 USAGE。

  • 注释不会随表重命名更新,内容里的旧表名会过时,需人工维护。

最佳实践 ​

  • 每张表、每个业务列都必须有注释。 这是团队协作的最低成本文档。
sql
COMMENT ON TABLE  orders        IS '订单主表:一笔订单一行';
COMMENT ON COLUMN orders.status IS '订单状态:pending/paid/shipped/cancelled';
  • 注释里写清枚举取值与单位,而不是重复列名。
sql
-- 不好:只是复述列名
COMMENT ON COLUMN orders.amount IS '订单金额';

-- 好:说清单位与约束
COMMENT ON COLUMN orders.amount IS '订单总金额(含税),单位:元,必须 > 0';
  • 注释与建表语句写在一起,并纳入迁移脚本,避免"建表时忘了写注释"。
sql
CREATE TABLE orders (...);
COMMENT ON TABLE  orders        IS '订单主表';
COMMENT ON COLUMN orders.amount IS '订单总金额(含税),单位:元';
  • 新项目主键统一用 GENERATED ... AS IDENTITY,不用 serial。 序列由系统管理,权限自动跟随,也能用 OVERRIDING SYSTEM VALUE 精确控制。
sql
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY
  • 手工创建的序列务必加 OWNED BY,避免孤儿序列。
sql
CREATE SEQUENCE order_no_seq OWNED BY orders.id;
  • 业务可见的编号用独立序列(如订单号),与主键解耦,便于按业务规则重构。
sql
CREATE SEQUENCE order_no_seq AS bigint START WITH 202600001 CACHE 20;
  • 高并发写入场景适当调大 CACHE,减少序列争用;对连续性有要求的场景保持 CACHE 1。
sql
-- 高并发、允许跳号
CREATE SEQUENCE hot_seq CACHE 100;

-- 要求连续(如发票号)
CREATE SEQUENCE invoice_seq CACHE 1 NO CYCLE;
  • 定期导出数据字典,把注释变成可交付的文档。
sql
-- 导出到文件
\copy (SELECT c.relname, a.attname, col_description(c.oid, a.attnum) FROM pg_class c JOIN pg_attribute a ON a.attrelid=c.oid WHERE c.relnamespace='public'::regnamespace AND c.relkind='r' AND a.attnum>0) TO 'data_dict.csv' WITH CSV HEADER
  • 把"无注释的列"纳入代码审查或巡检,用查询发现问题。
sql
SELECT c.relname, a.attname
FROM pg_class c
JOIN pg_attribute a ON a.attrelid = c.oid
LEFT JOIN pg_description d ON d.objoid = c.oid AND d.objsubid = a.attnum
WHERE c.relnamespace = 'public'::regnamespace AND c.relkind = 'r'
  AND a.attnum > 0 AND NOT a.attisdropped AND d.description IS NULL
ORDER BY c.relname, a.attnum;
  • 不要假设自增 ID 连续。 应用侧的业务编号规则应基于业务字段,而不是 id。

  • 定期清理孤儿序列,避免"看不见的对象"长期占位。

sql
SELECT c.relname AS orphan_sequence
FROM pg_class c
LEFT JOIN pg_depend d ON d.objid = c.oid AND d.deptype IN ('a', 'i')
WHERE c.relkind = 'S' AND d.objid IS NULL;
  • 权限变更后同步检查序列权限(GRANT USAGE, SELECT ON SEQUENCE),否则插入会因"无权取值"而失败。用 IDENTITY 可自动避免。
sql
-- serial 场景必须显式授权
GRANT USAGE, SELECT ON SEQUENCE users_id_seq TO app_user;
  • 把注释内容写进表结构评审清单:枚举值、单位、是否可空、外键指向,四项缺一不可。

下一节

继续学习:插入数据(INSERT)

参考链接 ​