GENERATED ALWAYS AS IDENTITY 列不可被 INSERT 或 UPDATE 显式赋值,仅能通过 BEFORE INSERT 触发器在系统生成前预设值;其执行顺序为触发器→系统填充→约束检查,适用场景限于强审计或全局有序需求。

GENERATED ALWAYS AS IDENTITY 列不能被 UPDATE 或 INSERT 显式赋值
这是最常踩的坑:只要列定义为 GENERATED ALWAYS AS IDENTITY,PostgreSQL 就会强制接管该列的值生成权。哪怕你只在 UPDATE 语句里碰了它一下,比如 UPDATE t SET id = 100 WHERE ...,就会立刻报错:cannot insert into column "id": Column is an identity column defined as GENERATED ALWAYS。
- INSERT 时也不能在 VALUES 或 SET 子句中给该列提供值(连
DEFAULT都不许显式写) - UPDATE 时哪怕只更新其他字段,但语句中意外包含了该列(如用
SET *或动态拼 SQL),也会触发错误 - 想绕过?不行——
OVERRIDING SYSTEM VALUE只对INSERT有效,且仅适用于GENERATED BY DEFAULT,对ALWAYS完全无效
BEFORE 触发器是唯一能“预设” GENERATED ALWAYS 值的地方
GENERATED ALWAYS 的值是在行插入/更新前由系统自动计算并填入的,但它本身不可写、不可改。如果你需要根据业务逻辑“决定”这个值(比如按租户分段编号、或从外部服务获取序列号),只能靠 BEFORE INSERT 触发器在系统生成前抢先赋值。
- 必须用
BEFORE INSERT FOR EACH ROW,并在函数中直接给NEW.column_name赋值 - 不能用
AFTER—— 那时系统已生成值,再改会触发“不能更新 identity 列”的错误 - 不能用
UPDATE语句去覆盖——哪怕目标是同一行,也会引发递归或权限拒绝 - 示例:
CREATE OR REPLACE FUNCTION set_custom_id() RETURNS trigger AS $$ BEGIN NEW.id := nextval('custom_seq'); RETURN NEW; END; $$ LANGUAGE plpgsql;再配CREATE TRIGGER tr_set_id BEFORE INSERT ON t FOR EACH ROW EXECUTE FUNCTION set_custom_id();
GENERATED ALWAYS 和 BEFORE 触发器共存时的执行顺序很关键
PostgreSQL 对 GENERATED ALWAYS AS IDENTITY 列的处理,发生在触发器之后、约束检查之前。也就是说:BEFORE 触发器 → 系统填充 identity 值(若未被触发器设置)→ 检查 NOT NULL / CHECK 等约束。
- 如果触发器没给
NEW.id赋值,系统会照常调用 sequence 生成值,不会报错 - 如果触发器赋了值,系统就跳过自动生成——此时你得确保该值满足后续所有约束(比如主键唯一性)
- 注意:触发器里调用
nextval()不会和 identity 自带的 sequence 冲突,但你要自己管理好 sequence 的起始值和步长,避免重复或跳号 - 别在触发器里做耗时操作(如远程 HTTP 请求),因为它是同步阻塞的,会影响写入吞吐
替代方案:什么时候该放弃 GENERATED ALWAYS,改用 GENERATED BY DEFAULT 或普通 SERIAL?
如果你发现总在写触发器绕过 GENERATED ALWAYS,或者频繁遇到“值已被占用”“序列不连续”等问题,说明这个设计可能过重了。
-
GENERATED BY DEFAULT AS IDENTITY更灵活:允许显式插入值(用于迁移、测试、合并数据),同时默认仍走 sequence - 纯
SERIAL(即integer PRIMARY KEY DEFAULT nextval(...))更透明、兼容性更好,尤其对接 ORM 或旧工具时不易出错 -
GENERATED ALWAYS的真正价值场景其实很窄:强审计要求(如绝对禁止人工干预主键)、多写端协同(靠 sequence 全局有序)、或配合生成列做衍生计算(如id_hex TEXT GENERATED ALWAYS AS (to_hex(id)) STORED) - 别为了“语法新”而用
GENERATED ALWAYS——它带来的限制比便利多得多
ALWAYS,你就交出了控制权;想拿回来,只能靠 BEFORE 触发器在系统动手前截胡——而且每次都要亲手校验逻辑闭环。










