触发器直接延长事务生命周期,因其非独立事务,而是强制包裹在主sql所属事务中,导致锁、undo log、binlog持续累积,显著加剧长事务风险。

触发器直接延长事务生命周期
触发器不是独立事务,它被强制包裹在触发它的 INSERT/UPDATE/DELETE 所属事务中。只要主 SQL 没提交,触发器里哪怕只执行一条 SELECT,整个事务就一直开着——锁、undo log、binlog 都持续累积。
常见错误现象:SHOW PROCESSLIST 显示状态为 Updating 或 Waiting for table metadata lock,而真正耗时的其实是触发器里那个没加索引的 SELECT COUNT(*) FROM audit_log WHERE event_time > DATE_SUB(NOW(), INTERVAL 7 DAY)。
- 事务时间 = 主 SQL 执行时间 + 所有触发器执行时间 + 后续未提交代码(如应用层
SLEEP(5)或 HTTP 调用) - MySQL 5.7+ 的
AFTER触发器若再UPDATE其他表,会把目标表的行锁也拖进同一事务边界 - 触发器内调用存储过程,该过程又开启隐式子事务(如 MySQL 的
autocommit=0模式下),会导致 undo log 无法及时 purge
触发器放大日志写入量与回滚段压力
一个看似简单的 INSERT INTO orders,若带 AFTER INSERT 触发器去更新 user_stats.last_order_at,实际产生的 redo/undo 日志量是两倍:主表变更 + 统计表变更。批量导入 10 万行时,这变成 20 万次写入,且全部绑定在同一个事务里。
后果很直接:INFORMATION_SCHEMA.INNODB_TRX 中的 TRX_ROWS_MODIFIED 值飙升;SHOW ENGINE INNODB STATUS 的 HISTORY LIST 长度突破 50000;磁盘上 ibdata1 或独立 undo_001 文件持续膨胀。
- 高并发下多个触发器争抢同一张统计表主键,极易触发
Deadlock found when trying to get lock,失败重试进一步推高日志量 - 触发器中含未走索引的
UPDATE(如UPDATE logs SET processed = 1无 WHERE),每次执行都扫全表并生成对应 undo 记录 - MySQL 8.0+ 的
innodb_undo_log_truncate不会生效,只要HISTORY LIST被长事务卡住,purge 线程就只能干等
触发器引发 binlog 巨型事务与主从延迟
主库上一个 UPDATE orders SET status = 'shipped' WHERE user_id IN (SELECT id FROM users WHERE region = 'CN'),若该语句触发了 2000 行的 AFTER UPDATE,那么 binlog 里记录的就是一个包含 2000 次衍生变更的巨型事务。从库 SQL 线程必须串行回放,无法并行化。
更糟的是:如果触发器里还调用了 INSERT INTO notify_queue,这条语句也会被塞进同一个 binlog event,导致单条 event 达到数 MB。此时 max_binlog_cache_size 若设为默认 4MB,就会报错 Multi-statement transaction required more than 'max_binlog_cache_size' bytes,事务直接中断。
- 主库
Seconds_Behind_Master突然跳涨几十分钟,大概率是某张高频表的触发器在批量场景下失控 -
SHOW SLAVE STATUS中SQL_Delay显示正在执行,但Relay_Log_File指向的其实是触发器生成的冗长事件 - 即使业务逻辑本身不慢,触发器让 binlog event 变“胖”,也会拖慢从库网络传输和解析阶段
如何快速确认触发器是长事务元凶
别靠猜。先查哪些表有触发器:SELECT * FROM INFORMATION_SCHEMA.TRIGGERS WHERE EVENT_OBJECT_TABLE = 'orders';再结合 performance_schema.events_statements_history_long(需提前开启 consumers)反查慢事务完整链路。
最实用的验证法:在维护窗口期临时禁用触发器,观察 innodb_row_lock_time_avg 和 HISTORY LIST 是否回落。禁用命令因版本而异:ALTER TABLE orders DISABLE TRIGGER trg_after_order_update(MySQL 8.0.19+)或改用 SET SQL_LOG_BIN = 0 + DROP/CREATE(仅限从库)。
- 禁用前务必确认该触发器是否承担关键审计或约束功能,避免误关导致数据越界
- 触发器逻辑若已耦合 ORM 的乐观锁字段(如
version),禁用后可能让应用层校验失效 - 上线前必须检查触发器是否重复创建(尤其 CI/CD 自动部署时),两个同名触发器同时启用会成倍放大风险
事务真正危险的地方,不在它“做了什么”,而在它“什么时候结束”——触发器让这个结束时间变得不可控、不可测、还带着一堆锁和日志一起拖尾。










