触发器会破坏事务边界、锁行为和可观测性,高并发下必然引发死锁、锁等待飙升及定位失能;其运行于主dml同一事务中,无法提交或回滚,仅能抛异常中断,且锁范围失控、调试困难、不支持复杂逻辑。

因为复杂业务逻辑塞进触发器会直接破坏事务边界、锁行为和可观测性——它不是“不太好”,而是会在高并发下必然引发死锁、锁等待飙升和定位失能。
触发器内无法控制事务边界
触发器运行在主 DML 语句的同一事务上下文中,你不能在里面写 COMMIT 或 ROLLBACK,否则报错 ERROR 1305 (42000): FUNCTION does not exist(实际是语法拦截)。想“校验失败就回滚”?唯一合法方式是用 SIGNAL SQLSTATE '45000' 主动抛异常。但这就意味着:所有被触发器牵连的表操作(比如日志写入、状态更新)都会随主语句一起回滚——而很多业务恰恰需要“主操作成功,日志异步落库”。
- 误写
ROLLBACK不仅失败,还会掩盖真实错误原因 - 调用的存储过程若含
COMMIT,同样触发ERROR 1422 - MySQL 5.6 及更早版本连
SIGNAL都不支持,只能靠插入不存在表等黑魔法中断,极不可靠
锁范围与等待时间完全失控
触发器里的 SQL 语句共享主事务的锁,且锁持有时间 = 主 DML 执行时间 + 触发器全部逻辑执行时间。哪怕只是多查一张配置表,也会把行锁延长几十毫秒。
-
BEFORE UPDATE里执行SELECT COUNT(*) FROM config→ 每次更新都重查,O(1) 变 O(n) -
AFTER INSERT中UPDATE users SET last_order_time = NOW() WHERE id = NEW.user_id→ 主事务锁订单行,触发器又锁用户行,另一条路径反着来,立刻触发Deadlock found when trying to get lock - 空触发器本身就有 0.1–0.3ms 固定开销;含
SELECT ... FOR UPDATE的触发器,5ms 就能让innodb_row_lock_time_avg翻倍
调试和可观测性几乎为零
触发器逻辑不进 EXPLAIN,不出现在慢日志的 SQL 文本里,SHOW PROCESSLIST 只显示 Executing trigger,连具体是哪个触发器都看不到。
-
EXPLAIN INSERT INTO orders完全不展示触发器内任何语句,优化主 SQL 对性能无改善 - 错误如
ERROR 1422或ERROR 1356从不指出是哪个触发器、哪一行代码引发 - 想确认某张表有没有触发器?必须手动查
INFORMATION_SCHEMA.TRIGGERS,没法自动化检测 - 并发压测时单条 SQL 测试完全失真:mysqlslap 测出 0.5ms,真实场景中因锁排队,
Threads_running卡在高位,TPS 腰斩
复杂逻辑天然不适合单行上下文
触发器按行触发,无法感知跨行聚合、时间窗口或外部状态。所谓“复杂校验”,很多根本不是数据库该干的事。
- “用户 1 小时内登录失败 5 次告警” →
BEFORE INSERT ON login_log拿不到历史记录,也拿不到当前会话时间范围 - “订单金额超阈值需人工审核” → 需查用户信用分、历史履约率、实时风控接口,这些都不是
NEW/OLD能承载的 - 三张表以上 JOIN 校验,或含 JSON 解析、正则匹配等操作,应直接放弃触发器方案
真正容易被忽略的点是:哪怕你把所有逻辑都封装进存储过程、只在触发器里留一句 CALL,只要那个过程涉及多表变更或耗时操作,锁和事务问题依然存在——触发器不是函数调用的隔离层,它是事务执行流的一部分。











