UPDATE触发器中必须用主键或唯一键JOIN取新旧值,不可依赖行序;主键为单列时ON d.id=i.id,复合键需全部列匹配;无主键应先加唯一约束;禁止SELECT @var=col FROM inserted,须用JOIN或聚合处理。

UPDATE触发器里怎么安全取新旧值
必须用主键或唯一键 JOIN,不能靠行序或 TOP 1。SQL Server 不保证 Inserted 和 Deleted 表的行物理顺序一致,盲目按顺序匹配会把张三的旧数据和李四的新数据连在一起。
- 主键是单列:写
ON d.id = i.id - 主键是复合键(如
(order_id, line_no)):ON d.order_id = i.order_id AND d.line_no = i.line_no缺一不可 - 没主键?别硬连——先加唯一约束,否则逻辑必然出错
- 只查需要的列,比如要对比
status变更,就写SELECT d.id, d.status AS old_status, i.status AS new_status,别用SELECT *
为什么不能用 SELECT @var = col FROM inserted
因为 inserted 是表,不是变量。哪怕只更新一行,@var 也可能被赋任意一行的值(SQL Server 不指定顺序),多行更新时更是只取其中一行,其余全丢。
- 错误写法:
SELECT @price = LineTotal FROM inserted→@price值不可控 - 正确方向:用
JOIN或聚合,比如同步订单头金额:UPDATE po SET SubTotal = po.SubTotal + ISNULL(SUM(idt.LineTotal), 0) FROM PurchaseOrderHeader po INNER JOIN inserted idt ON po.PurchaseOrderID = idt.PurchaseOrderID GROUP BY po.PurchaseOrderID, po.SubTotal - 校验类逻辑(如“价格不能为负”)要用集合判断:
IF EXISTS (SELECT 1 FROM inserted WHERE LineTotal ,而不是查单行
AFTER 和 INSTEAD OF 触发器中两张表行为差异
inserted 和 deleted 表存在,但语义和可用性不同,混用会直接报错或逻辑失效。
- AFTER UPDATE:两张表都有数据,行数相等,可放心
JOIN - AFTER INSERT:
inserted有数据,deleted为空 → 别写IF NOT EXISTS (SELECT 1 FROM deleted)来判断是否是插入,它永远为空 - AFTER DELETE:
deleted有数据,inserted为空 → 此时做新旧对比毫无意义 - INSTEAD OF 触发器:允许访问
text/ntext/image列;AFTER 触发器则禁止访问这些类型列,会报错
性能容易被忽视的几个点
看似轻量的 inserted/deleted 表,在宽表+大批量更新时可能成瓶颈,尤其当触发器里做 INSERT INTO ... SELECT * 或复杂 JOIN 时。
- 宽表(30+ 列)一次更新上万行 →
SELECT d.*, i.*会把所有列复制两遍进内存,压力陡增 - 日志表写入频繁?避免在触发器里直接
INSERT INTO audit_log SELECT ... FROM inserted,考虑异步或队列表方案 - 用
@@ROWCOUNT判断影响行数,再决定是否执行耗时逻辑,比如单行更新走轻量路径,批量走简化逻辑 - 触发器嵌套深度默认是 32 层,如果多个表互相触发,很容易超限导致失败,得主动用
TRIGGER_NESTLEVEL()控制
inserted 和 deleted,等于拿业务一致性赌 SQL Server 的运气。

















