视图无法自动填充审计字段,因其只读且不执行default或触发器逻辑;审计字段必须在基表定义并由基表的default和before触发器维护,视图仅透出字段。

视图本身不能自动填充审计字段——它不参与写入,也不执行默认值或触发器逻辑。所有“自动填充”必须发生在底层表上,视图只负责暴露已存在的字段。
为什么视图里设DEFAULT或触发器都无效
PostgreSQL 视图是只读查询封装,不存储数据,也不支持 DEFAULT、GENERATED、BEFORE INSERT 等写入级约束和机制。你尝试在视图定义中写 created_at TIMESTAMPTZ DEFAULT NOW() 会直接报错:ERROR: syntax error at or near "DEFAULT"。视图上建的触发器(如 INSTEAD OF INSERT)可以模拟插入行为,但必须手动调用序列、赋值时间戳,并插入到底层表——不是“自动”,而是“显式代理”。
正确做法:审计字段必须在基表定义并由基表机制维护
视图只是透出这些字段的窗口,真正的填充逻辑必须落在基表上:
-
created_at和updated_at字段要在基表中声明为TIMESTAMPTZ DEFAULT NOW() -
updated_at的自动更新必须靠BEFORE UPDATE触发器实现,函数体写NEW.updated_at := NOW(); RETURN NEW; - 如果基表已有数据但没默认值,不能靠视图补,默认缺失只能通过
BEFORE INSERT触发器兜底(但这是基表层面的修复,和视图无关) - 视图定义里只需原样
SELECT created_at, updated_at,不要加NOW()或CURRENT_TIMESTAMP计算——那返回的是查询时刻,不是数据写入时刻
INSERT INTO 视图时如何让审计字段生效
只有使用 INSTEAD OF INSERT 触发器,并在触发器函数中显式赋值时间戳,才能让“通过视图插入”也带上审计时间:
- 触发器函数必须调用
NOW()并赋给NEW.created_at和NEW.updated_at,再插入底层表 - 不能依赖基表的
DEFAULT—— 因为INSTEAD OF触发器绕过了基表的约束检查,DEFAULT不触发 - 示例关键行:
NEW.created_at := NOW(); NEW.updated_at := NOW(); INSERT INTO users_real SELECT (NEW).*; - 漏掉
RETURN NEW会导致插入静默失败;漏掉对NEW的赋值,字段会是NULL(如果允许)或报错(如果NOT NULL且无默认)
容易被忽略的并发与一致性细节
多个客户端同时通过视图插入时,看似简单的 NOW() 赋值没问题,但要注意:
-
NOW()在同一事务内始终返回相同时间戳,保证事务内一致性;但不同事务之间毫秒级可能重复——这不是问题,除非业务要求绝对唯一时间戳(此时需结合序列或clock_timestamp(),但后者破坏事务一致性,慎用) - 如果底层表有
ON CONFLICT或唯一约束,触发器里没做异常捕获,整个INSERT INTO view会回滚,但错误信息可能不明确(比如报 “null value in column created_at violates not-null constraint”,实际是触发器没赋值) - 应用若用连接池(如 pgbouncer),
SET app.user_id = '123'这类会话变量需在每次获取连接后重置,否则审计日志里的操作人可能错乱










