SQL Server的DML触发器天然支持多行操作,因inserted/deleted是内存中的行集表而非单行变量;若用SELECT @val=col FROM inserted会仅取任意一行值导致逻辑错误,正确做法是基于JOIN或聚合进行集合运算。

SQL Server 的 DML 触发器天然支持多行操作,但直接按单行逻辑写(比如用 SELECT @val = LineTotal FROM inserted)会出错——它只取一行值,其余被忽略,更新结果不可靠。
为什么 INSERTED/DELETED 是行集,不是单行变量
触发器里的 inserted 和 deleted 是临时内存表,不是标量。哪怕你只执行 UPDATE Products SET Price = 100 WHERE ID = 5,inserted 里也是一张含 1 行的表;而 UPDATE Products SET Price = Price * 1.1 WHERE CategoryID IN (1,2,3) 会让 inserted 包含多行。
常见错误现象:
- 用
SELECT @x = col FROM inserted赋值后,@x 只拿到某一行的值(SQL Server 不保证哪一行),后续计算全错 - 在触发器里写
IF EXISTS (SELECT 1 FROM inserted WHERE Status = 'A') UPDATE ...看似没问题,但漏掉了其他行的业务约束
正确写法:全部基于 JOIN 或聚合,避开逐行假设
核心原则是把 inserted 当成普通表参与集合运算。不要试图“遍历”,而是“匹配”或“汇总”。
例如同步更新订单头 SubTotal:
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;
关键点:
-
GROUP BY必须包含目标表主键和所有被更新的列(除非用SUM()后直接赋值) - 不能写
SET SubTotal += idt.LineTotal—— 这会把多行 LineTotal 当作单个值加,报错或截断 - 如果需要对每行插入做独立校验(如价格不能为负),用
IF NOT EXISTS (SELECT 1 FROM inserted WHERE LineTotal ,而不是查单行
DELETE 和 UPDATE 触发器中 Deleted 表的用法差异
deleted 在 UPDATE 和 DELETE 中都存在,但语义不同:
-
UPDATE触发器中:deleted存旧值,inserted存新值,两者行数一定相等、主键相同 -
DELETE触发器中:deleted存被删行,inserted为空,此时不能依赖两表 JOIN 做对比
典型误用:
-- ❌ UPDATE 触发器里这样写会导致逻辑错乱(deleted 和 inserted 行序不保证对应) UPDATE t SET LastModified = GETDATE() FROM TargetTable t INNER JOIN inserted i ON t.ID = i.ID INNER JOIN deleted d ON t.ID = d.ID WHERE i.Value <> d.Value; -- 看似比对新旧值,但 JOIN 不保证行级对齐
正确做法是显式关联主键后,在 WHERE 中用子查询或 EXISTS 判断变化:
-- ✅ 安全比对
UPDATE t SET LastModified = GETDATE()
FROM TargetTable t
WHERE t.ID IN (SELECT ID FROM inserted)
AND t.ID IN (SELECT ID FROM deleted)
AND EXISTS (
SELECT 1 FROM inserted i
INNER JOIN deleted d ON i.ID = d.ID
WHERE i.ID = t.ID AND i.Value <> d.Value
);性能陷阱:别在触发器里用游标或循环
即使你用 DECLARE cur CURSOR FOR SELECT ... FROM inserted 能跑通,也极大概率引发阻塞和超时。SQL Server 触发器运行在事务上下文中,游标会锁住 inserted 表元数据,且无法并行。
替代方案只有两个:
- 坚持用
JOIN/GROUP BY/EXISTS等集合操作(首选) - 如果业务逻辑实在复杂(比如要调用外部 API),把关键字段写入队列表,由后台作业异步处理,触发器只做轻量记录
最后注意:inserted 和 deleted 是内存结构,没索引。大批次更新(比如 10 万行)时,JOIN 性能取决于你是否在关联字段上有索引——别忘了在业务表主键上建好索引,否则触发器本身就成了瓶颈。

















