触发器本身不直接消耗undo,但会延长事务、放大dml量,导致undo无法及时释放;其行级执行、嵌套调用及与主事务强绑定特性,显著加剧undo日志膨胀和purge滞后。

触发器本身不直接耗Undo,但会把隐式事务拖长、放大DML量,让Undo在不该留的地方死扛着不释放。
触发器让事务“看不见地变长”
很多人以为触发器只是“顺手跑一下”,实际它完全运行在主事务上下文中——AFTER INSERT 触发器里再UPDATE 1000行,这1000行的Undo就和原始INSERT绑在同一事务里。只要主事务没提交,所有这些Undo都得留着。
- 应用层只发了一条
INSERT INTO orders,但触发器悄悄执行了UPDATE inventory SET qty = qty - 1+INSERT INTO audit_log,Undo消耗翻了3倍 -
FOR EACH ROW触发器在批量插入时被调用N次,每次生成独立Undo记录,比FOR EACH STATEMENT多出一个数量级开销 - 触发器里调用存储过程或远程DBLINK,会把分布式事务也拉进当前Undo生命周期,
COMMIT前无法清理
Oracle和MySQL对触发器Undo的处理差异
Oracle里触发器DML直接计入父事务的used_ublk;MySQL则把触发器操作塞进同一trx_id,导致HISTORY LIST LENGTH暴涨更快——尤其当触发器含SELECT ... FOR UPDATE时,gap lock范围扩大,旧版本保留更多。
- Oracle查
v$transaction时,used_ublk高但sql_id显示的是主SQL,不是触发器里的语句,容易漏掉根因 - MySQL中
INFORMATION_SCHEMA.INNODB_TRX的trx_rows_modified包含触发器改的行数,但trx_query为空,需结合performance_schema.events_statements_history反查 - MySQL 8.0.23+启用
innodb_undo_log_truncate=ON后,触发器产生的Undo段仍可能卡在ACTIVE状态,因为purge线程无法区分“主DML完成但触发器未退栈”
最容易被忽略的坑:触发器嵌套 + 异常路径
触发器A调触发器B,B里又抛异常回滚——这时Undo不会按预期释放。Oracle会保留整个嵌套链的Undo直到最外层事务结束;MySQL则可能让HISTORY LIST卡在中间状态,purge线程跳过这部分记录。
- 没写
EXCEPTION块的PL/SQL触发器,一旦报错(如ORA-01422),事务挂起但连接不释放,Undo持续占位 - MySQL触发器里
INSERT INTO error_log失败,整个事务回滚,但已生成的Undo要等后台purge,期间新事务仍被阻塞 - 用
PRAGMA AUTONOMOUS_TRANSACTION在触发器里开自治事务,看似解耦,实则新增一套Undo管理,反而加剧空间碎片
真正要盯的不是触发器代码有多短,而是它让事务从“毫秒级提交”变成“分钟级悬停”的那个临界点——哪怕只多一行UPDATE,只要没COMMIT,Undo就停不下来。











