触发器中推荐用current_timestamp,因其是sql标准写法、语义明确且兼容性好;now()虽行为一致但属mysql扩展,迁移至postgresql等数据库时可能报错;严禁使用sysdate(),因其返回执行时刻时间,会导致批量更新结果不一致。

触发器里用 NOW() 还是 CURRENT_TIMESTAMP?
两者在大多数 MySQL 场景下效果一致,但语义和兼容性有差别:CURRENT_TIMESTAMP 是 SQL 标准写法,明确表示“当前时间戳”,且在定义列默认值时更安全;NOW() 是 MySQL 扩展函数,行为相同但可读性稍弱。触发器中建议统一用 CURRENT_TIMESTAMP,避免在迁移到其他数据库(如 PostgreSQL)时因函数名不识别而报错。
注意:不要写 SYSDATE() —— 它返回语句执行时刻的时间,而非事务开始时刻,在批量更新中可能产生不一致的值。
UPDATE 触发器必须检查字段是否真被修改
直接无条件更新时间戳会导致“伪修改”:哪怕其他字段没变,只要执行了 UPDATE 语句,时间戳也会刷新。这会干扰审计逻辑、破坏缓存一致性、甚至让乐观锁误判。
- 用
IF OLD.column_name != NEW.column_name判断具体字段变化(注意 NULL 比较要用IS DISTINCT FROM或显式处理,MySQL 中可用NOT (OLD.col NEW.col)) - 若需任意字段变更就更新时间戳,可用
IF ROW_COUNT() > 0配合触发器前先做SELECT对比,但更简单可靠的做法是:只在业务明确需要“最后变更时间”的字段上设触发器,而非全表兜底 - PostgreSQL 用户注意:
OLD和NEW是行级记录,支持OLD.* IS DISTINCT FROM NEW.*一次性比较整行
MySQL 中创建 BEFORE UPDATE 触发器的最小可行写法
必须用 BEFORE UPDATE,不能用 AFTER —— 后者无法修改 NEW 行的值。以下是在 users 表上自动更新 updated_at 的标准写法:
DELIMITER $$
CREATE TRIGGER users_update_updated_at
BEFORE UPDATE ON users
FOR EACH ROW
BEGIN
IF NOT (OLD.updated_at NEW.updated_at) THEN
SET NEW.updated_at = CURRENT_TIMESTAMP;
END IF;
END$$
DELIMITER ;
关键点:NOT (OLD.updated_at NEW.updated_at) 正确处理 NULL;SET NEW.updated_at = ... 直接赋值,不是 UPDATE 语句;触发器名需全局唯一,建议带表名前缀。
PostgreSQL 用户别漏掉 EXECUTE FUNCTION 语法
PG 的触发器定义分两步:先写函数,再绑定触发器。函数里必须返回 NEW,否则触发器会中断更新:
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = CURRENT_TIMESTAMP;
RETURN NEW;
END;
$$ language 'plpgsql';
<p>CREATE TRIGGER update_users_updated_at
BEFORE UPDATE ON users
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();</p>
容易踩的坑:EXECUTE PROCEDURE 在新版本 PG 中已弃用,必须用 EXECUTE FUNCTION;函数末尾的 RETURN NEW 缺失会导致整条 UPDATE 失败(返回 NULL 行);如果只想在某些条件下更新,把赋值逻辑包在 IF 里即可,但 RETURN NEW 仍需保留。
跨数据库移植时最常被忽略的是触发器执行时机(BEFORE/AFTER)、NULL 比较方式、以及函数返回要求——这些细节不匹配,触发器要么不生效,要么直接报错中断业务。










