before触发器中修改new字段仅影响待写入的临时值,主事务回滚则全部失效;所谓“生效”实为误判,常见于日志记录、外部操作或不可回滚动作(如序列号分配)。

触发器里改了NEW,但主事务回滚后“好像生效了”
BEFORE 触发器中对 NEW 字段的赋值(比如 NEW.updated_at := NOW())确实会生效——但这只是影响“即将插入/更新的那行数据的临时值”,它本身不写库、不落盘。一旦主事务回滚,整行记录根本没进表,NEW 的修改自然也消失。你看到的“生效”,往往是误判:比如日志写了、其他表被显式 UPDATE 了、或触发器里调用了不可回滚的操作(如序列号分配、发消息、写外部文件)。
实操建议:
- 区分操作类型:
NEW.field = value只作用于本行待写入值;UPDATE other_table SET ...是独立 DML,若没被主事务包裹,就真执行了 - 检查触发器是否混用了可回滚与不可回滚动作:比如在同一个触发器里既改
NEW.status,又调用NEXT VALUE FOR seq_log_id—— 后者永远不回滚 - MySQL 中注意
NEW字段名大小写敏感(尤其lower_case_table_names=0时),NEW.User_ID和NEW.user_id可能指向不同字段,赋值失败却不报错
触发器里调了 NEXT VALUE FOR,主事务回滚后序列号却丢了
NEXT VALUE FOR 是典型的不可回滚操作。SQL Server 和 PostgreSQL 都明确设计为:每次调用即永久推进序列计数器,无论后续是否 ROLLBACK。这不是 bug,是机制使然。你看到“执行成功但数据没变”,很可能是因为触发器里先拿了序列号生成单据号,再因校验失败 THROW 或 RAISERROR,结果单据号已消耗,主表却空空如也。
实操建议:
- 验证是否真有跳号:查
sys.sequences(SQL Server)或pg_sequences(PostgreSQL)里的last_value,对比实际插入行数 - 别在触发器里直接调
NEXT VALUE FOR——改用应用层预占(sp_sequence_get_range)或延迟到事务提交前一刻 - 如果必须放触发器里,至少加日志:
INSERT INTO debug_seq_log VALUES (NEW.id, NEXT VALUE FOR seq_order),方便事后比对
触发器里写了 INSERT/UPDATE,但主事务回滚后这些操作还在
这是最危险的情况:触发器内部显式执行的 DML(如 INSERT INTO log_table)默认参与外层事务,按理该随主事务一起回滚。但如果触发器函数返回 NULL(PostgreSQL AFTER 触发器)、或用了 START TRANSACTION(MySQL 错误地开启嵌套事务)、或调用了自治事务(Oracle 特性,SQL Server/PG 不支持),就可能脱离主事务控制,造成“半生效”。
实操建议:
- PostgreSQL 中,AFTER 触发器函数必须返回
OLD或NEW;返回NULL会导致后续逻辑跳过,但已执行的语句不会自动回滚 - MySQL 触发器里禁止写
START TRANSACTION或COMMIT——这会报错ERROR 1305 (42000): SAVEPOINT does not exist或静默失败 - SQL Server 触发器无法开启新事务,
@@TRANCOUNT增加只反映嵌套深度,ROLLBACK总是回滚到最外层,但前提是没提前RETURN
主备切换后触发器行为不一致,误以为“回滚失效”
从库上触发器看似“执行了但没效果”,常因 @@sql_log_bin = 0 导致 binlog 不记录,而触发器本身仍在从库执行(尤其 SQL SECURITY DEFINER 且定义者用户存在时)。此时主库回滚了,但从库因没收到 binlog 事件,触发器仍按自己逻辑跑完,造成状态分裂。
实操建议:
- 切完主备立刻连从库执行
SELECT @@sql_log_bin;,值为 0 就说明当前会话不写 binlog,触发器行为不可复制 - 用
SHOW CREATE TRIGGER对比主从定义,重点看DEFINER用户是否存在、SQL SECURITY是否一致、有没有用UUID()或NOW()这类非确定性函数 - ROW 格式 binlog 不保证触发器逻辑一致——它只传最终行变更,不传触发器中间步骤。把时间/随机逻辑提到应用层生成后再传入
真正难排查的,是那些看起来“什么都没做”却悄悄消耗了序列、发出了消息、或写了外部日志的触发器。它们不报错、不写表、不抛异常,只在业务对账时露出破绽。盯住 NEXT VALUE FOR、RAISERROR 后有没有 ROLLBACK、以及 SHOW CREATE TRIGGER 里那几行不起眼的 DEFINER 和 SQL SECURITY,比反复查日志更有效。











