PostgreSQL中必须用BEFORE触发器配合RETURN NEW实现updated_time自动更新,漏写RETURN NEW会导致整行被丢弃;函数语言须为plpgsql,字段推荐使用TIMESTAMPTZ类型。

PostgreSQL 里必须用触发器才能自动更新 updated_time 字段,没有像 MySQL 那样的 ON UPDATE CURRENT_TIMESTAMP 语法。
触发器函数必须返回 NEW,且不能漏写 RETURN NEW
这是最常踩的坑:函数体里写了 NEW.updated_time = CURRENT_TIMESTAMP,但忘了最后一行 RETURN NEW。结果触发器执行后整行数据变 NULL,插入或更新直接失败。
- 函数语言必须是
plpgsql(不是sql) - 函数返回类型必须是
TRIGGER -
NEW是只读引用,修改它字段值后必须显式返回,否则 PostgreSQL 认为操作被拒绝 - 示例函数:
CREATE OR REPLACE FUNCTION update_updated_time() RETURNS TRIGGER AS $$ BEGIN NEW.updated_time = CURRENT_TIMESTAMP; RETURN NEW; -- 这行不能少! END; $$ LANGUAGE plpgsql;
BEFORE UPDATE 是唯一可靠时机,AFTER 不行
想在更新后改时间戳?不行。AFTER UPDATE 触发器里 NEW 已不可修改,改了也无效。只有 BEFORE UPDATE 才能拦截并注入新值。
- 如果还要支持
INSERT时也设updated_time(通常和created_time一致),就把触发事件改成BEFORE INSERT OR UPDATE - 注意:触发器对
UPDATE ... SET col = col这种“无实质变更”的语句也会触发,除非你手动加判断逻辑 - 不推荐用
RULE替代——它在某些场景(如ON CONFLICT DO UPDATE)下不生效,行为不如触发器稳定
EXECUTE PROCEDURE 和 EXECUTE FUNCTION 的版本差异
PostgreSQL 11 及以前必须用 EXECUTE PROCEDURE;12+ 支持 EXECUTE FUNCTION,但两者语法完全等价,只是关键字不同。别因为版本写错导致建触发器报错 syntax error at or near "FUNCTION"。
- 11 及更早版本(比如 Ubuntu 18.04 自带的 10.23):
CREATE TRIGGER set_updated_time BEFORE UPDATE ON users FOR EACH ROW EXECUTE PROCEDURE update_updated_time();
- 12+ 版本可写成:
CREATE TRIGGER set_updated_time BEFORE UPDATE ON users FOR EACH ROW EXECUTE FUNCTION update_updated_time();
- 如果你不确定版本,查一下:
SELECT version();
字段类型建议用 TIMESTAMP WITH TIME ZONE
用 TIMESTAMP(无时区)看似简单,但一旦数据库服务器时区变更,历史时间戳含义就乱了。而 TIMESTAMPTZ 存的是 UTC 时间,读取时按会话时区自动转换,语义更清晰、迁移更安全。
- 建表时这样定义:
created_time TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, updated_time TIMESTAMPTZ
-
CURRENT_TIMESTAMP返回的就是TIMESTAMPTZ,和字段类型天然匹配 - 避免用
now()或clock_timestamp()做默认值——它们在事务内行为不同,CURRENT_TIMESTAMP是标准且可预测的选择
真正麻烦的不是写几行 SQL,而是所有业务路径(包括 INSERT ... ON CONFLICT DO UPDATE、批量 UPDATE、ORM 自动生成语句)都得经过这个触发器。一旦函数里有异常(比如字段名拼错),整个 DML 就静默失败——所以务必在测试环境用真实数据流验证一遍。

















