sql server触发器按语句触发而非行触发,因此inserted表始终是本次insert语句产生的完整结果集快照,单行插入时含1行,多行插入时含所有插入行;它结构与目标表一致,只读且生命周期限于触发器执行期。

触发器里Inserted表为什么不是单行?
因为SQL Server的触发器是按语句触发,不是按行触发。只要你执行的是批量操作(比如INSERT INTO t VALUES (),(),()或INSERT ... SELECT),哪怕只写一条INSERT语句,只要它插入多行,触发器里的inserted表就会包含所有被插入的行——它本质是“这次INSERT语句产生的结果集快照”,不是“当前正在处理的某一行”。
INSERTED表结构和数据来源完全由原始语句决定
inserted表字段与目标表完全一致,内容就是本次INSERT实际写入的数据副本。它不经过约束检查前的原始值,也不含默认值展开后的结果(除非用了DEFAULT关键字显式触发)。常见误区是以为它只反映“最后一条”,其实它是完整集合:
- 单行
INSERT→inserted有1行 - 100行
INSERT ... SELECT→inserted有100行 - 带
OUTPUT子句的INSERT →inserted仍存在,且与OUTPUT返回内容逻辑一致
用错Inserted会导致UPDATE逻辑全错
很多人在AFTER INSERT触发器里写UPDATE t SET x = x + 1 WHERE id = (SELECT id FROM inserted),这在多行时会报错或误更新——因为子查询返回多行不能直接用于=比较。正确做法必须用JOIN或EXISTS:
UPDATE t SET total_amount = t.total_amount + i.amount FROM orders t INNER JOIN inserted i ON t.order_id = i.order_id;
漏掉JOIN而用子查询、或没加WHERE条件导致全表扫描,是线上事故高频原因。
临时表JOIN比循环处理更可靠
如果触发器逻辑复杂(比如要关联其他表查状态、调用函数计算),别在触发器里写游标或WHILE循环——SQL Server对行级循环优化极差,且容易锁升级。更稳的方式是把inserted当普通表用:
- 建索引(如
CREATE UNIQUE INDEX IX_tmp ON #tmp(id))加速后续JOIN - 先INSERT到带索引的临时表,再用
UPDATE ... FROM #tmp批量修正主表 - 避免在触发器里调用远程服务器、外部程序或长事务
多行inserted不是异常,是设计使然;把它当“输入参数表”来用,而不是当成“单行上下文”,才能写出可扩展的触发器逻辑。










