mysql触发器中old代表修改前值、new代表修改后值,仅在对应操作中合法:insert只可用new,delete只可用old,update两者皆可;old始终只读,new仅在before中可修改。

MySQL触发器里用OLD和NEW取原始值与新值
在MySQL的BEFORE UPDATE或AFTER UPDATE触发器中,OLD.字段名代表修改前的值,NEW.字段名代表修改后的值。这是唯一合法的引用方式,不能用别名、子查询或JOIN来“查出”旧值——触发器执行时原记录已处于变更过程中,只有这两个隐式上下文可用。
常见错误是试图在INSERT触发器里用OLD(不存在),或在DELETE触发器里用NEW(不存在)。MySQL会直接报错:ERROR 1363: There is no OLD row in on INSERT trigger 或类似提示。
-
BEFORE INSERT:只可用NEW,可修改其值(如设置默认时间、校验并抛错) -
BEFORE UPDATE:OLD和NEW都可用,且可赋值给NEW来覆盖待写入值 -
BEFORE DELETE:只可用OLD,常用于归档或权限检查
PostgreSQL用OLD/NEW但语法更严格
PostgreSQL同样提供OLD和NEW,但它们是行类型(record),必须显式声明变量或直接用点号访问字段,比如OLD.id、NEW.name。不支持像MySQL那样在SQL语句中直接嵌套使用OLD作为子查询源。
容易踩的坑是:在UPDATE触发器中对NEW字段赋值后,没加RETURN NEW(或RETURN NULL阻止操作),导致更新被静默丢弃。PostgreSQL要求触发器函数必须返回TRIGGER类型值,否则报错:ERROR: trigger function must return type trigger。
- 函数体里修改
NEW字段后,必须RETURN NEW才能生效 - 想阻止操作?赋值
NEW := NULL再RETURN NULL - 字段名区分大小写,若建表时用了双引号,触发器里也得用双引号包裹
SQL Server没有OLD/NEW,改用inserted/deleted伪表
SQL Server不用OLD/NEW,而是通过两个内存伪表:inserted(含新值)和deleted(含旧值)。它们只在触发器作用域内存在,行为类似临时表,支持SELECT、JOIN甚至WHERE条件过滤。
关键区别在于:单行操作和多行操作下,这两个表都可能含多行数据。不能假设SELECT TOP 1 id FROM inserted就拿到当前行——必须用JOIN或EXISTS关联主键处理批量更新场景。
-
INSERT触发器:只有inserted有数据,deleted为空 -
DELETE触发器:只有deleted有数据,inserted为空 -
UPDATE触发器:两个表都有数据,且行数相同,主键值一一对应 - 别在触发器里用
@@ROWCOUNT判断单行——它反映的是整个DML影响行数,不是触发器当前处理的逻辑行
跨数据库移植时最容易忽略的兼容性点
不同数据库对“同一字段新旧值是否允许相等”处理不一致。例如MySQL在BEFORE UPDATE中,即使NEW.status = OLD.status,触发器仍会执行;而PostgreSQL在UPDATE SET x=x时,如果未实际变更任何字段,触发器根本不会触发(取决于fire_on_change行为,默认不触发)。SQL Server则一定触发,哪怕所有字段值都没变。
另一个隐藏雷区:某些ORM(如Hibernate)批量更新时绕过触发器(直接发UPDATE ... WHERE),或用INSERT ... ON CONFLICT替代UPDATE,导致你以为该跑的触发器根本没跑。上线前务必用真实DML语句验证,别只靠单元测试里的单条save()调用。











