sql server触发器是语句级而非行级,批量插入时只触发一次且inserted表包含所有新行;sqlbulkcopy默认不触发触发器,需显式设置firetriggers=true;应避免游标遍历inserted,优先使用集合操作。

SQL Server触发器在批量插入时只触发一次,是正常行为,不是 bug —— 它本来就是语句级(statement-level)的,不是行级(row-level)。
SQL Server 触发器天生就是语句级的
无论你用 INSERT INTO t VALUES (1),(2),(3) 还是 INSERT INTO t SELECT ... FROM #temp,只要是一条 INSERT 语句,哪怕影响 10 万行,AFTER INSERT 或 INSTEAD OF INSERT 都只执行一次。此时 INSERTED 表里就装着全部新行,而不是“一行一触发”。
- 常见误判:看到日志只写了一条、或业务表只更新了第一行,就以为触发器“没跑全”
- 根本原因:代码里写了
SELECT @id = id FROM INSERTED,多行时@id取值不确定(SQL Server 不报错,但结果不可控) - 验证方法:在触发器开头加一句
INSERT INTO debug_log(row_count) SELECT COUNT(*) FROM INSERTED,看实际进来的行数
SqlBulkCopy 批量导入默认不触发触发器
如果你用的是 SqlBulkCopy 类(比如 C# 程序直连 SQL Server 导数据),那触发器压根不会执行——这是设计使然,不是配置漏了。
- 原因:
SqlBulkCopy走 TDS 协议直写数据页,绕过 T-SQL 引擎层,所以不走约束、不走触发器、也不走默认值逻辑 - 启用触发器:必须显式设置
bulkCopy.FireTriggers = true;否则即使你写了AFTER INSERT,也完全静默 - 替代方案:先用
SqlBulkCopy导入临时表,再用INSERT INTO target_table SELECT ... FROM #temp,这条 INSERT 会正常触发
别用游标遍历 INSERTED,除非你真想拖慢系统
看到“只触发一次”,很多人立刻写游标逐行处理 INSERTED,这在 SQL Server 里很常见,但属于性能反模式。
- 问题:游标强制序列化,锁持有时间拉长,事务日志暴涨,10 万行可能从几百毫秒变成几十秒
- 正确做法:用集合操作,例如
UPDATE t2 SET status = 'imported' FROM t2 INNER JOIN INSERTED i ON t2.id = i.id - 前提:
t2.id必须有索引,否则 JOIN 退化为嵌套循环,性能反而更差 - 如果业务逻辑真要逐行调外部 API 或复杂判断——说明不该放触发器里,该挪到应用层异步队列或定时批任务
真正容易被忽略的点是:**触发器是否被调用,和它是否“生效”,是两回事。** 你看到日志里有记录,不代表业务逻辑覆盖了所有行;你看到 INSERTED 有 5000 行,不代表你写的 SELECT TOP 1 或变量赋值能拿到全部数据。语句级触发器的思维惯性,比语法本身更难绕出来。











