MySQL用BEFORE UPDATE触发器自动更新updated_at最可靠,须用NEW.updated_at=NOW();PostgreSQL需先建RETURNS trigger函数再调用;SQL Server的INSTEAD OF需重写完整UPDATE逻辑;跨库应统一时区或应用层生成时间戳。

MySQL 中用 BEFORE UPDATE 触发器更新 updated_at
直接在表上定义触发器,让每次 UPDATE 操作前自动刷新时间戳,比应用层手动维护更可靠。关键点是必须用 BEFORE UPDATE,而不是 AFTER UPDATE——后者无法修改当前行数据。
常见错误是把触发器写成 AFTER,结果 updated_at 完全没变;或者漏掉 FOR EACH ROW,导致语法报错 ERROR 1363: There is no NEW row in on INSERT statement。
实操建议:
- 确保目标字段类型为
TIMESTAMP或DATETIME,且允许NULL或有默认值(TIMESTAMP类型可设DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,但触发器优先级更高,会覆盖该行为) - 触发器名要唯一,比如
trg_users_update_updated_at,避免和已有触发器冲突 - 语句中必须用
NEW.updated_at = NOW(),不能写成SET updated_at = NOW()(缺少NEW.前缀会导致列未识别)
CREATE TRIGGER trg_users_update_updated_at BEFORE UPDATE ON users FOR EACH ROW SET NEW.updated_at = NOW();
PostgreSQL 里用 pg_trigger 实现等效逻辑
PostgreSQL 不支持直接在触发器体中赋值,必须配合函数使用。这是和 MySQL 最大区别:你得先建一个函数,再让触发器调用它。
容易踩的坑包括:函数返回类型写成 void 却忘了加 RETURN NULL,导致触发器执行失败;或者函数里误用 OLD.updated_at 而不是 NEW.updated_at,结果时间戳根本没更新。
实操建议:
- 函数必须声明为
RETURNS trigger,且最后一行必须是RETURN NEW - 触发器定义时要显式指定
EXECUTE FUNCTION(v12+)或EXECUTE PROCEDURE(旧版),不能省略 - 如果表有多个时间戳字段(如
created_at和updated_at),可在同一函数里一并处理,避免建多个触发器
CREATE OR REPLACE FUNCTION update_updated_at_column() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at = NOW(); RETURN NEW; END; $$ language 'plpgsql'; CREATE TRIGGER update_users_updated_at BEFORE UPDATE ON users FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
SQL Server 的 INSTEAD OF UPDATE 要谨慎替代
SQL Server 没有 BEFORE 触发器,最接近的是 INSTEAD OF UPDATE。但它会完全取代原 UPDATE 语句,所以你必须在触发器里手动重写整个更新逻辑,否则数据不会变。
这不是“加一行时间戳”那么简单,而是“接管整条 UPDATE”。很多开发者卡在这里:只写了 SET NEW.updated_at = GETDATE(),却忘了 UPDATE target_table SET ... FROM inserted,结果表数据全丢了。
实操建议:
- 优先考虑用
DEFAULT约束 +UPDATE语句显式更新字段,仅当业务逻辑复杂才用触发器 - 若必须用触发器,务必在
INSTEAD OF体中完整拼出UPDATE语句,并用inserted表做数据源 - 测试时注意:对同一行连续两次
UPDATE,第二次可能因未正确引用inserted导致updated_at停滞
跨数据库兼容性陷阱:不要依赖 NOW() 或 CURRENT_TIMESTAMP
虽然多数数据库都支持 NOW(),但语义不一致:MySQL 的 NOW() 是语句开始时间,PostgreSQL 的 NOW() 是事务开始时间,而 SQL Server 的 GETDATE() 是执行时刻。高并发下毫秒级差异可能暴露业务逻辑漏洞。
更隐蔽的问题是时区:MySQL 默认用系统时区,PostgreSQL 默认用会话时区,SQL Server 取决于服务器设置。如果你的微服务部署在不同区域,updated_at 可能出现倒序。
实操建议:
- 统一在应用层生成时间戳(如
new Date().toISOString()),由 SQL 语句带入,彻底规避数据库函数差异 - 如果坚持用触发器,至少在所有环境统一设置数据库时区(如 MySQL 的
time_zone='+00:00') - 避免在触发器里调用存储过程或外部函数,它们可能隐式改变时区或事务状态
触发器看着简单,但一旦涉及多库部署、主从同步或 CDC 工具(如 Debezium),updated_at 字段就很容易成为数据不一致的源头。别低估那一行赋值背后的上下文依赖。










