-- 注释:写入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 '售价(含税),单位:元';
注释支持几乎所有对象类型,写法统一为 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 存放于共享目录,因此它的注释对所有库可见,其他对象的注释只在本库内可见。
-- 一次性导出一张表所有列的注释SELECT a.attnum AS col_no, a.attname AS column_name, format_type(a.atttypid, a.atttypmod) AS data_type, d.description AS commentFROM pg_attribute aLEFT JOIN pg_description d ON d.objoid = a.attrelid AND d.classoid = 'pg_class'::regclass AND d.objsubid = a.attnumWHERE a.attrelid = 'products'::regclass AND a.attnum > 0 AND NOT a.attisdroppedORDER BY a.attnum;
-- 查找"没有注释"的列:治理数据库文档的常用手段SELECT c.relname AS table_name, a.attname AS column_nameFROM pg_class cJOIN pg_attribute a ON a.attrelid = c.oidLEFT JOIN pg_description d ON d.objoid = c.oid AND d.classoid = 'pg_class'::regclass AND d.objsubid = a.attnumWHERE c.relnamespace = 'public'::regnamespace AND c.relkind = 'r' AND a.attnum > 0 AND NOT a.attisdropped AND d.description IS NULLORDER BY c.relname, a.attnumLIMIT 20;
-- 查询所有序列的"水位",用于发现即将耗尽的自增 IDSELECT schemaname || '.' || sequencename AS sequence, last_value, max_value, round(last_value::numeric / max_value * 100, 2) AS used_pctFROM pg_sequencesWHERE max_value IS NOT NULLORDER BY used_pct DESC NULLS LASTLIMIT 10;
sql
-- 找出所有与列失去绑定的"孤儿序列"SELECT c.relname AS orphan_sequenceFROM pg_class cLEFT JOIN pg_depend d ON d.objid = c.oid AND d.deptype IN ('a', 'i')WHERE c.relkind = 'S' AND d.objid IS NULL;
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 TABLECREATE 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 '订单号全局唯一';
-- 一次性导出所有表与列的注释(可直接存成文档)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_commentFROM pg_class cJOIN pg_attribute a ON a.attrelid = c.oidWHERE c.relnamespace = 'public'::regnamespace AND c.relkind = 'r' AND c.relname IN ('users', 'orders') AND a.attnum > 0 AND NOT a.attisdroppedORDER 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.descriptionFROM pg_class cJOIN pg_attribute a ON a.attrelid = c.oidJOIN pg_description d ON d.objoid = c.oid AND d.classoid = 'pg_class'::regclass AND d.objsubid = a.attnumWHERE 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';
-- 导出到文件\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.attnameFROM pg_class cJOIN pg_attribute a ON a.attrelid = c.oidLEFT JOIN pg_description d ON d.objoid = c.oid AND d.objsubid = a.attnumWHERE c.relnamespace = 'public'::regnamespace AND c.relkind = 'r' AND a.attnum > 0 AND NOT a.attisdropped AND d.description IS NULLORDER BY c.relname, a.attnum;
不要假设自增 ID 连续。 应用侧的业务编号规则应基于业务字段,而不是 id。
定期清理孤儿序列,避免"看不见的对象"长期占位。
sql
SELECT c.relname AS orphan_sequenceFROM pg_class cLEFT 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;
注释(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_descriptionCOMMENT ON SCHEMA s IS '...'pg_description(classoid = pg_namespace)COMMENT ON DATABASE d IS '...'pg_shdescription(共享目录)COMMENT ON SEQUENCE s IS '...'pg_descriptionCOMMENT ON FUNCTION f(int) IS '...'pg_description(需带参数类型)COMMENT ON TYPE t IS '...'pg_descriptionCOMMENT ON TRIGGER tr ON t IS '...'pg_descriptionCOMMENT ON EXTENSION e IS '...'pg_descriptionCOMMENT 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会删除已有注释,而不是写入空串。serial不是类型:它是integer + 序列 + DEFAULT 表达式 + OWNED BY的语法糖。GENERATED ... AS IDENTITY才是 SQL 标准写法。语法与用法
最简可运行示例:
详细说明
COMMENT ON 的完整用法
注释支持几乎所有对象类型,写法统一为
COMMENT ON <对象类型> <对象标识> IS '<内容>':注意
COMMENT ON DATABASE存放于共享目录,因此它的注释对所有库可见,其他对象的注释只在本库内可见。删除注释就是把它设为
NULL:查询注释:三种方式
方式一:psql 元命令
\d+(最直观)方式二:专用函数(推荐脚本使用)
方式三:直接查目录表(可批量导出)
序列(SEQUENCE)基础
序列是一个独立的数据库对象,用一条
nextval原子地取下一个值。序列不属于事务,这是它与
IDENTITY都会被"跳号"的根源:setval用于手动设定,例如数据迁移后同步序列位置:serial vs IDENTITY vs SEQUENCE
三者都能实现自增,但实现方式与可靠性差异很大:
serialGENERATED ... AS IDENTITYSEQUENCEGRANT USAGEALWAYS时禁止,需OVERRIDING SYSTEM VALUE表名_列名_seq表名_列名_seqOWNED BY)OWNED BY的作用是:删除该列或该表时,序列被自动一起删除,避免留下孤儿序列。serial与IDENTITY都自动带这个绑定。序列的 cache 与性能
CACHE n让每个会话一次预留 n 个值,减少系统目录更新次数,在高并发插入时能显著提升吞吐。代价是崩溃或多会话场景下跳号更明显。默认CACHE 1最"省号",但并发写入时序列本身会成为热点。实战示例
场景:为一个新建的订单系统补全"可维护性"——① 给所有表与列加中英双语注释;② 创建一个对外可见的订单号序列;③ 把序列与列绑定;④ 事后导出完整数据字典。
第 1 步:建表
第 2 步:加表与列注释
第 3 步:用
\d+验证注释已生效第 4 步:创建业务订单号序列
第 5 步:把序列绑定到列
第 6 步:用序列生成业务订单号
第 7 步:导出完整数据字典
第 8 步:用注释反向查找字段
第 9 步:清理
序列确实随表一起删掉了——这正是
OWNED BY的价值。注意事项
COMMENT ON不校验内容,也不影响执行。 但注释会被pg_dump导出,写错信息会长期误导使用者。COMMENT ON需要对象的所有者权限。 非所有者执行会报错。objsubid)关联,删列后序号会错位。 直接查pg_description时必须关联pg_attribute.attnum,而不是硬编码列号。COMMENT ON DATABASE存于共享目录,库被删后注释也一起消失,与其他对象的可见性不同。序列的号不会被回滚退还。 这是"跳号"的根源,业务上不应假设订单号连续。
currval在未调用nextval的会话里会报错。serial创建的序列是独立对象,可能被误删。 删掉序列后插入会立刻失败。新项目用
IDENTITY可避免这类问题。CACHE越大跳号越明显。 高并发场景下CACHE 100可能一次跳几十个号。CYCLE)。bigint序列基本不会耗尽,但smallint/integer有可能。序列不是事务安全的"无间隙"生成器,也不提供跨行回滚语义。需要严格连续编号时必须用表锁自行实现。
setval需要UPDATE权限(PostgreSQL 8.1+),不只是USAGE。注释不会随表重命名更新,内容里的旧表名会过时,需人工维护。
最佳实践
GENERATED ... AS IDENTITY,不用serial。 序列由系统管理,权限自动跟随,也能用OVERRIDING SYSTEM VALUE精确控制。OWNED BY,避免孤儿序列。CACHE,减少序列争用;对连续性有要求的场景保持CACHE 1。不要假设自增 ID 连续。 应用侧的业务编号规则应基于业务字段,而不是
id。定期清理孤儿序列,避免"看不见的对象"长期占位。
GRANT USAGE, SELECT ON SEQUENCE),否则插入会因"无权取值"而失败。用IDENTITY可自动避免。下一节
继续学习:插入数据(INSERT)
参考链接