sql server中获取update前后值必须通过inner join关联inserted(存新值)和deleted(存旧值)表,且只能用主键或唯一约束列匹配,不可依赖行序;两表结构相同、行数相等,应只select所需列以提升性能。

直接说结论:不同数据库用的机制完全不同,不能套用同一套写法。MySQL 用 OLD 和 NEW 伪记录,PostgreSQL 要靠触发器函数参数传入 OLD/NEW 变量,SQL Server 则必须查 inserted 和 deleted 临时表——写错一个,轻则字段取不到,重则触发器报错中断操作。
MySQL 中怎么安全读 OLD 和 NEW
它们不是变量,是只读伪记录,只能在触发器体里用点号访问具体字段,比如 OLD.email、NEW.status。
-
OLD只在UPDATE和DELETE触发器中可用;在INSERT里访问会报错:ERROR 1363 (HY000): There is no OLD row in on INSERT trigger -
NEW在INSERT和UPDATE中可用,DELETE里访问同样报错 - 字段名必须和原表定义**完全一致**:大小写、下划线、空格、关键字都得一模一样;含空格或关键字(如
order)必须用反引号包裹,OLD.`order status`才对,OLD.order_status就失效 - 判断字段是否真被改过,别用
!=直接比:NULL 字段会导致条件失效,要用空安全比较NOT (OLD.col NEW.col) -
BEFORE UPDATE中可以修改NEW.col(比如自动更新updated_at),但改OLD.col会直接报ERROR 1362;AFTER UPDATE里两者都只读,改了也白改
PostgreSQL 触发器函数里怎么拿到 OLD 和 NEW
PG 没有隐式伪记录,必须在函数体里显式声明参数接收,并返回对应行值。
- 函数定义时要写
RETURNS TRIGGER,且必须返回NEW(INSERT/UPDATE)或OLD(DELETE),否则触发器会中断原操作 - 函数体内直接用
OLD.id、NEW.email访问字段,但注意:字段名大小写敏感,且必须和表定义一致 - 想标准化字段(比如转小写),必须在
BEFORE触发器里改NEW.email := lower(NEW.email);AFTER里这么写是静默无效的 - 整行比较不能写
IF OLD != NEW THEN,得用ROW(OLD.*) IS DISTINCT FROM ROW(NEW.*),否则 NULL 字段会让判断失准 - 如果表有 JSONB 或大字段,直接插
OLD整行进审计表可能触发 TOAST 开销,建议只取必要字段
SQL Server 触发器怎么关联 inserted 和 deleted
它没有 OLD/NEW,全靠两张内存临时表,而且它们是“表”,不是单行记录——多行更新时必须显式 JOIN 主键,否则数据错位。
-
UPDATE操作会同时填充inserted(新值)和deleted(旧值),但二者无隐式顺序或一一对应关系 - 正确写法是用主键 INNER 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 - 复合主键必须写全:
ON d.order_id = i.order_id AND d.line_no = i.line_no,漏一个就匹配失败 - 不能在触发器里写
SELECT * FROM 表名查原表——可能引发递归或死锁;需要关联其他表时,优先用EXISTS而非IN,避免 NULL 导致逻辑跳过 -
IF UPDATE(col)只能检测该列是否出现在SET子句里,不代表值真变了;要判断值差异,还得查deleted和inserted
最容易被忽略的兼容性陷阱
看着都是 “OLD/NEW”,实际行为差很远:MySQL 允许字段名大小写不敏感(取决于 lower_case_table_names 配置),PostgreSQL 默认严格区分;SQL Server 的 inserted/deleted 表不支持索引、不可被其他会话访问;而 SQLite 只在 BEFORE 触发器里支持 OLD,AFTER 里直接不可用。
更隐蔽的是 NULL 处理:MySQL 中 SET NEW.col = NULL 在 NOT NULL 字段上会直接报错中断,PostgreSQL 却允许设 NULL,只要最终 INSERT/UPDATE 语句本身不违反约束就行——这种差异在跨库迁移时几乎必然踩坑。










