触发器本身不导致重复写入,但会隐式放大重复风险:AFTER INSERT中逐行执行INSERT、嵌套调用含INSERT的存储过程、或未配UNIQUE约束兜底,均会导致日志或数据重复;须通过trigger_invoked字段标记、检查触发器定义及行数匹配来定位。

触发器本身不会“导致”重复写入,但它会让重复写入更难被发现、更难被拦截——因为真正插入的不是你写的那条 INSERT,而是触发器里偷偷执行的另一条。
为什么触发器里的 INSERT 会悄悄重复?
常见场景是:你在 AFTER INSERT 触发器里又执行了一次 INSERT INTO log_table,但没做任何去重判断。如果主表被批量插入(比如 INSERT INTO orders SELECT ... FROM temp),触发器就会为每一行都执行一次日志插入,而你可能只预期“记录一次操作”。更隐蔽的是,有些触发器会调用存储过程,而该过程内部又做了 INSERT,形成隐式嵌套写入。
- MySQL 的
FOR EACH ROW触发器对批量插入的每一行都触发一次,不是对整条语句触发一次 - SQL Server 的
AFTER INSERT触发器中,Inserted表可能包含多行,但开发者常误写成SELECT TOP 1 ... FROM Inserted,漏掉其余行,或反过来——本想记一笔总操作,却循环插了 N 条 - PostgreSQL 的
FOR EACH ROW同样逐行触发;若用FOR EACH STATEMENT,则需确认逻辑是否真能覆盖所有情况(比如UPDATE ... FROM可能影响多行,但触发器只执行一次)
怎么快速确认重复日志是不是触发器写的?
别翻应用日志,直接查数据库。核心思路是:看“谁写了这条日志”,而不是“谁触发了它”。
- 在日志表(如
audit_log)里加一个trigger_invoked字段,默认0,在触发器里显式设为1;这样查SELECT * FROM audit_log WHERE trigger_invoked = 1就一目了然 - MySQL 下,用
SHOW TRIGGERS LIKE 'your_table'看定义,重点检查有没有INSERT INTO或调用含INSERT的存储过程 - SQL Server 中,查
sys.triggers和sys.dm_exec_trigger_stats,对比total_execution_count和主表的插入频次:如果后者每天 100 次,前者却有 5000 次,大概率触发器在内部循环或被误配为多行触发 - PostgreSQL 中,用
\d trg_name查触发器定义,注意WHEN (condition)是否遗漏,导致不该触发时也执行
触发器重复写入最常踩的三个坑
不是逻辑写错,而是机制理解偏差。
-
BEFORE INSERT里做INSERT—— 这在 MySQL 中非法(报错ER_NO_RAID),但在 SQL Server 和 PostgreSQL 中允许,结果就是主插入 + 触发器插入,变成两条记录,且事务回滚时可能只滚掉主表,留下脏日志 - 在触发器里调用另一个含
INSERT的存储过程,且该过程没加IF NOT EXISTS或唯一约束兜底,等于套娃式重复 - 把
ON DUPLICATE KEY UPDATE或INSERT IGNORE写在触发器里,但目标表没建UNIQUE索引——冲突不触发,重复照插
修复前必须验证的两件事
改触发器前,先确认它到底在什么条件下运行、写了多少行。
- 临时把触发器里的
INSERT改成SELECT,比如INSERT INTO log VALUES (...) → SELECT 'would insert', NEW.id, NOW(),再跑一遍业务流程,看输出行数是否匹配预期 - 在触发器开头加
SELECT COUNT(*) FROM Inserted(SQL Server)或SELECT ROW_COUNT()(MySQL),把结果写进调试日志表,确认每次触发实际处理了几行数据
触发器不是黑盒,它的执行粒度、上下文和副作用都必须可测。一旦发现它写入量和主表变更量不一致,问题就出在这里,而不是上游应用或网络重试。

















