SQL Server和MySQL的DML触发器均为语句级,批量操作只触发一次;BULK INSERT和LOAD DATA INFILE绕过触发器;UPDATE无实际变更时MySQL 8.0+跳过触发器;TRUNCATE是DDL,不激活任何DML触发器。

触发器只执行一次,但你以为它逐行运行
SQL Server 和 MySQL 的 DML 触发器全是语句级(statement-level),不是行级(row-level)。INSERT INTO t VALUES (1),(2),(3) 影响 3 行,触发器只调用 1 次,INSERTED 里装着全部 3 行——不是“进 3 次、跑 3 次”。很多人在触发器里写 SELECT @id = id FROM INSERTED,结果只拿到其中一行的值,其余数据被覆盖或丢弃。
- 错误现象:单行插入正常,批量插入时日志少一半、关联更新只生效几条、校验逻辑静默失效
- 根本原因:把集合当单行处理,
@var = col在多行结果下不报错,但语义上只保留末行 - 正确做法:用
INSERT INTO log_table SELECT ... FROM INSERTED或UPDATE t2 SET x = 1 FROM t2 JOIN INSERTED i ON t2.id = i.id
BULK INSERT 和 LOAD DATA INFILE 根本不走触发器路径
BULK INSERT(SQL Server)和 LOAD DATA INFILE(MySQL)是绕过完整 DML 执行栈的高速导入机制,默认跳过触发器、外键检查、甚至部分约束。它们不是“触发器失效”,而是压根没被调用。
- 验证方式:在触发器里加
INSERT INTO debug_log,执行BULK INSERT后查日志表为空 → 就是这个原因 - SQL Server 中
ENABLE TRIGGER对BULK INSERT完全无效 - MySQL 中官方文档明确写:
LOAD DATA INFILE does not activate triggers. - 替代方案:先
BULK INSERT到临时表#staging,再INSERT INTO real_table SELECT * FROM #staging(显式 DML,触发器生效)
UPDATE 无实际变更时,MySQL 8.0+ 直接跳过触发器
如果执行 UPDATE t SET name = name WHERE id = 1,MySQL 8.0+ 在默认 sql_mode 下会判定为“无效更新”,连触发器入口都不进。这不是 bug,是优化行为。
- 检查当前模式:
SELECT @@sql_mode;,重点看是否含STRICT_TRANS_TABLES - 触发器没日志?先跑
SHOW WARNINGS;,常能捕获类似Warning | 1287 | 'UPDATE' without a WHERE clause is deprecated的提示 - 避免陷阱:不要依赖字段“看起来变了”就触发,而要确保
SET子句中至少有一个字段值真正不同 - 跨库操作漏写库名前缀也会导致静默失败,比如
INSERT INTO audit_log应写成INSERT INTO sys_log.audit_log
TRUNCATE 是 DDL,天然不激活任何 DML 触发器
TRUNCATE TABLE 不是 DELETE 的快捷方式,它是 DDL 操作,不生成 INSERTED/DELETED 伪表,也不走事务日志的逐行记录路径。所有 AFTER INSERT、BEFORE UPDATE 等 DML 触发器对它完全无感。
- 现象:清空表后审计日志没新增、状态字段没重置、关联缓存没刷新
- 别信“我刚删了数据,触发器应该跑”,
TRUNCATE就是设计为不触发 - 真要走触发器逻辑,必须改用
DELETE FROM table_name(可带WHERE或不带) - SSIS 或应用代码里用了
TRUNCATE + BULK INSERT组合?整个链路里触发器全程静默,得从源头改
复杂点在于:这些行为都不是配置错误或权限问题,而是数据库引擎层的设计契约。你没法“修好”BULK INSERT 让它触发,也没法让 TRUNCATE 突然支持触发器——它们本来就不该干这事。

















