触发器重复执行是设计不当的必然结果,主因是跨表写操作或外部逻辑调用导致链式触发,须在触发器首行用pg_trigger_depth()、context_info()等主动截断,且严禁在其中update业务表。

触发器重复执行不是“偶发问题”,而是设计不当的必然结果——只要触发器里有跨表写操作或调用外部逻辑,就极可能被多次触发,尤其在批量 UPDATE 或主从复制场景下。
为什么AFTER UPDATE触发器会自己触发自己
最常见原因是触发器内执行了另一条 UPDATE,而目标表恰好也定义了同类型触发器。比如:
- 用户表
users有AFTER UPDATE触发器,它往日志表user_logs插入记录 - 但
user_logs表上也建了AFTER INSERT触发器,又反过来 UPDATE 了users的统计字段 - 这就形成
users → user_logs → users的链式调用
MySQL 直接报 ERROR 1442;SQL Server 和 PostgreSQL 不报错,但会真实执行多轮,直到达到递归深度上限或超时。
用 pg_trigger_depth() 或 CONTEXT_INFO 主动截断
不能靠“禁用递归开关”这种全局配置,必须在触发器体内做运行时判断:
- PostgreSQL:开头加
IF pg_trigger_depth() > 1 THEN RETURN; END IF; - SQL Server:用
CONTEXT_INFO()设标记,如IF CONTEXT_INFO() = 0x54524947474552 RETURN - MySQL:设
SET SESSION max_sp_recursion_depth = 1(注意只能在连接初始化时设,不能在触发器里写)
重点:这个判断必须放在触发器第一行,且不能被任何 IF 分支包裹——否则分支未命中时仍会继续执行。
别在触发器里 UPDATE 其他业务表
哪怕那张表没定义触发器,也可能因外键级联、复制延迟、或应用层重试机制导致重复写入:
- 触发器里写
UPDATE stats SET count = count + 1 WHERE user_id = NEW.id→ 批量更新 100 行,就会执行 100 次,count 多加 100 次 - 主库执行一次 UPDATE,从库回放时又触发一遍(MySQL row-based replication 下仍可能)
- 推荐做法:只写中间表,比如
INSERT INTO sync_queue (table_name, pk, op) VALUES ('users', NEW.id, 'update')
真正同步由幂等服务消费,不依赖触发器执行次数。
批量 UPDATE 时触发器行为和单行完全不同
很多人测试只用 UPDATE ... WHERE id = 123,上线后跑 UPDATE ... WHERE status = 'pending' 就出事:
- 触发器对每一行都独立执行一次,不是“整个语句触发一次”
- 如果触发器里有
SELECT COUNT(*) FROM orders WHERE user_id = NEW.user_id,10 万行更新 = 查 10 万次 orders 表 - 更危险的是:
BEFORE UPDATE中用SELECT ... INTO @var赋值,只会取到第一行结果,其余行全按默认值处理
真要处理批量,必须面向 inserted(SQL Server)、NEW 集合(PG)或显式 JOIN,而不是逐行假设。











