update触发器中old代表更新前行、new代表更新后行,二者为只读伪记录,须用old.col、new.col访问字段;before中可修改new字段,after中仅可读;判断变更需空安全比较,如not (old.col new.col)。

MySQL触发器里用 OLD 和 NEW 拿字段值
在 UPDATE 触发器中,OLD 代表更新前的行,NEW 代表更新后的行。它们不是变量,而是伪记录(pseudo-record),只能在触发器体中直接点字段访问,比如 OLD.name、NEW.email。
常见错误是试图把 OLD 或 NEW 当成普通变量赋值或判空——它们不能被赋值,也不能直接 IS NULL 判断整行;必须对具体字段操作。
-
BEFORE UPDATE中可读可写NEW字段(改了会影响实际更新结果),但OLD只读 -
AFTER UPDATE中OLD和NEW都只读,适合审计日志 - 对未修改的字段,
OLD.col = NEW.col成立;想判断某字段是否真被改过,得显式比较:IF OLD.status != NEW.status THEN ...
PostgreSQL触发器要靠 OLD / NEW + ROW 类型变量
PG 的触发器函数是独立的 PL/pgSQL 函数,没有隐式 OLD/NEW。你得在函数定义时声明参数接收它们:
CREATE OR REPLACE FUNCTION log_user_change()
RETURNS TRIGGER AS $$
BEGIN
IF TG_OP = 'UPDATE' THEN
INSERT INTO user_audit (user_id, old_email, new_email, changed_at)
VALUES (OLD.id, OLD.email, NEW.email, NOW());
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
注意三点:
- 函数必须返回
NEW(INSERT/UPDATE)或OLD(DELETE),否则触发器会中断操作 -
TG_OP是内置变量,值为'INSERT'、'UPDATE'或'DELETE',别硬写字符串比较 - 如果表有
JSONB或大字段,直接存OLD/NEW整行可能触发 TOAST 开销;建议只取必要字段
SQL Server触发器依赖 inserted 和 deleted 临时表
SQL Server 不提供行级伪记录,而是用两个内存临时表:inserted(含新值)和 deleted(含旧值)。UPDATE 操作会同时填充两张表,INSERT 只填 inserted,DELETE 只填 deleted。
典型误用是假设 inserted 和 deleted 总是一一对应——其实它们是表,可能多行,必须用 JOIN 关联主键才能匹配前后值:
SELECT d.id, d.name AS old_name, i.name AS new_name FROM deleted d INNER JOIN inserted i ON d.id = i.id WHERE d.name != i.name;
- 不能在触发器里对原表做
SELECT * FROM 表名,可能引发递归或死锁 - 如果业务逻辑需要查其他表,优先用
EXISTS而非IN,避免NULL导致意外跳过 - 触发器执行期间,
inserted/deleted表不可被其他会话访问,也不支持索引——大数据量更新时性能容易掉
跨数据库通用坑:NULL 值比较和字符集隐式转换
所有数据库都默认用三值逻辑处理 NULL:直接写 OLD.col != NEW.col 在任一字段为 NULL 时结果是 UNKNOWN,条件不成立。必须显式处理:
- MySQL/PG:用
IS DISTINCT FROM(PG 原生支持,MySQL 8.0.22+ 支持)或手写(OLD.col != NEW.col) OR (OLD.col IS NULL) != (NEW.col IS NULL) - SQL Server:用
ISNULL(OLD.col, '') != ISNULL(NEW.col, ''),但要注意类型一致(比如INT不能直接和空字符串比) - 如果字段是
VARCHAR且库/表字符集不同(如 utf8mb4 vs latin1),OLD.col = NEW.col可能因隐式转换失败报错,建议统一字符集或显式CONVERT
触发器里最隐蔽的问题往往不在逻辑,而在这些边界值和类型细节上——一次 NULL 比较漏判,就可能让关键审计字段永远不入库。











