PostgreSQL中GENERATED ALWAYS AS列禁止INSERT时显式赋值,建表要求表达式IMMUTABLE,生成列实时计算无缓存,STORED可索引但不可逆更改。
PostgreSQL 不允许显式向 GENERATED ALWAYS AS 列写入值,哪怕你传了 NULL 或默认值。只要 INSERT 语句里列名列表中包含了该列,或者 VALUES 中对应位置给了值(哪怕为空),就会触发这个错误。
常见误操作:
* 或未排除计算列INSERT ... SELECT * 且源表结构含同名列但非生成列正确做法是:显式列出所有**非生成列**,确保 GENERATED ALWAYS AS 列不在列名列表中。例如:
INSERT INTO users (name, email, created_at) VALUES ('Alice', 'a@example.com', NOW());而不是:
INSERT INTO users (id, name, email, full_name, created_at) VALUES (DEFAULT, 'Alice', 'a@example.com', NULL, NOW()); -- ❌ id 和 full_name 都可能触发错误
GENERATED ALWAYS AS 要求表达式必须是 IMMUTABLE 或至少 STABLE,但 PostgreSQL 实际只接受 IMMUTABLE —— 否则建表会报错:ERROR: generation expression is not immutable。
典型踩坑点:
NOW()、CURRENT_TIMESTAMP、RANDOM() 等 volatile 函数IMMUTABLE 属性TO_CHAR(created_at, 'YYYY-MM') —— 表面看是确定的,但 TO_CHAR 默认是 STABLE,需显式 cast 或换函数修复方式:
EXTRACT(YEAR FROM created_at) 替代 TO_CHAR(...)(前者是 IMM
IMMUTABLE,且内部不能调用任何非 IMMUTABLE 函数生成列的值在查询时实时计算,不是物理存储(除非加了 STORED),所以它“看起来没变”只是因为每次 SELECT 都重新算。但如果你发现 UPDATE 基础列后,SELECT 里生成列还是旧值,那大概率是你在用缓存层(比如应用层 ORM 缓存、连接池的 statement cache)或没重查。
注意点:
pgbouncer 在 transaction 模式下,且语句被重用,可能因 plan cache 导致结果延迟(少见但存在)update_fields)若没刷新实例,会继续显示旧对象属性,和数据库无关验证是否真有问题?直接连 psql 执行:
UPDATE users SET name = 'Bob' WHERE id = 1;
SELECT id, name, full_name FROM users WHERE id = 1;
如果这里 full_name 是对 name 的拼接,它一定立刻更新 —— 否则说明表达式本身依赖了不可变字段或写错了逻辑。
不加 STORED:每次读都计算,节省空间,但增加 CPU 开销,尤其表达式复杂或高频查询时;加了 STORED:值物理写入磁盘,INSERT/UPDATE 时多一次计算+写入,但读快、可建索引、支持部分谓词下推。
选型关键判断点:
first_name || ' ' || last_name —— 不加 STORED 通常够用STORED,否则报错 index on generated column must be stored
WHERE 或 JOIN 这个列?加 STORED + 索引能显著提升性能一旦加了 STORED,后续改回非 STORED 无法直接 ALTER,只能 DROP COLUMN + ADD COLUMN 重建,数据会丢失(除非提前导出)。这点容易被忽略。