SQL Server用DELETED和INSERTED临时表获取新旧值,须主键JOIN避免错位;MySQL 8.0+用OLD./NEW.前缀,仅BEFORE可修改NEW.值。

SQL Server 里用 DELETED 和 INSERTED 临时表取旧值新值
SQL Server 的 AFTER UPDATE 或 INSTEAD OF UPDATE 触发器中,系统自动提供两个内存临时表:DELETED(含更新前的行)和 INSERTED(含更新后的行)。它们结构与原表一致,但不支持索引或约束,也不能在触发器外访问。
常见错误是直接写 SELECT * FROM DELETED 却没加 JOIN 关联,导致多行更新时结果错位。必须用主键或唯一列显式关联两表。
- 单行更新可直接查,但代码要预留多行兼容性
- 若表无主键,得靠所有字段比对(性能差,易出错)
-
DELETED在INSERT触发器中为空,INSERTED在DELETE触发器中为空——别混用
CREATE TRIGGER tr_log_price_change ON products AFTER UPDATE AS BEGIN INSERT INTO price_audit (product_id, old_price, new_price, changed_at) SELECT d.id, d.price, i.price, GETDATE() FROM DELETED d INNER JOIN INSERTED i ON d.id = i.id -- 必须有这个 JOIN WHERE d.price != i.price; END;
MySQL 8.0+ 用 OLD. 和 NEW. 前缀访问字段
MySQL 不提供临时表,而是在 BEFORE UPDATE 和 AFTER UPDATE 触发器中通过 OLD.字段名 和 NEW.字段名 直接引用。注意:只有 BEFORE 触发器能修改 NEW. 值(比如做数据清洗),AFTER 中 NEW. 已写入磁盘,再改无效。
容易踩的坑是误在 AFTER 触发器里给 NEW.price = NEW.price * 1.1,这行代码不会报错,但完全没效果。
-
OLD.在INSERT触发器中所有字段为NULL;NEW.在DELETE触发器中为NULL - 字段名必须带前缀,写
price会报错“Unknown column 'price'” - 如果更新语句只改了部分列(如
UPDATE t SET name='x'),未涉及的字段在NEW.中仍为原值,不是NULL
OLD
- 在函数里用
OLD.id、NEW.name访问字段,语法同普通变量 - 若字段可能为
NULL,用IS DISTINCT FROM比!=更安全(因为NULL != NULL返回NULL而非TRUE) - 触发器函数返回
NULL会跳过原操作(BEFORE时),返回NEW才继续
CREATE OR REPLACE FUNCTION log_salary_change()
RETURNS TRIGGER AS $$
BEGIN
IF OLD.salary IS DISTINCT FROM NEW.salary THEN
INSERT INTO salary_history(emp_id, old_salary, new_salary)
VALUES (OLD.id, OLD.salary, NEW.salary);
END IF;
RETURN NEW; -- 必须返回,否则 UPDATE 失败
END;
$$ LANGUAGE plpgsql;
跨数据库迁移时最常被忽略的兼容性点
不同数据库对“未更新字段”的处理逻辑不一致:SQL Server 的 DELETED/INSERTED 总是包含完整行;MySQL 的 OLD./NEW. 字段值取决于实际 SQL 语句;PostgreSQL 的 OLD/NEW 也是完整行,但函数需显式声明 RETURNS TRIGGER。这意味着,把 MySQL 触发器直接迁到 PostgreSQL 会因语法和语义双失败。
更隐蔽的问题是时间精度:SQL Server 默认 GETDATE() 只到毫秒,PostgreSQL CURRENT_TIMESTAMP 默认微秒,MySQL NOW(6) 才到微秒——审计日志里如果靠时间戳排序,跨库对比会乱序。
- 不要假设
OLD和NEW在所有数据库中行为一致 - 涉及计算字段或默认值时,MySQL 的
NEW.col可能已是计算后值,而 SQL Server 的INSERTED.col是语句最终写入值,中间逻辑可能已被绕过 - 测试必须覆盖多行更新场景,单行 OK 不代表生产安全










