触发器导致QPS断崖式下跌,根本原因就在于它把批量操作硬生生拆成了逐行事务——这可不是小事,锁竞争、WAL暴增、并行优化失效一股脑全来了。更坑的是,这些耗时还不进慢日志,监控根本抓不到。先亮一张实测数据图,感受一下直观冲击。

看QPS衰减曲线,不是单条语句耗时
想评估触发器的影响,别指望用EXPLAIN分析主SQL——它连触发逻辑的影子都看不见。真正的杀伤力藏在吞吐量的断崖式下跌里。压测时必须分层跑并发:从16线程起步,逐步加到64、128,盯着QPS找拐点。多数系统在64线程后,触发器的损耗会从20%直接跳到40%以上——这不是线性增长,是锁竞争和WAL暴增叠加出来的“非线性冲击波”。
常见的迷惑行为:slow_query_log里翻不出慢SQL,但SHOW ENGINE INNODB STATUS里的innodb_row_lock_time_a vg飙得老高;performance_schema.events_statements_history_long里确实能捞到一堆带TRIGGER的事件,可耗时根本不单独算——等于白记录。
- 务必将
long_query_time设成0抓全量慢日志,再用EXPLAIN FORMAT=TREE确认触发器内SQL有没有全表扫描 - MySQL 8.0+ 上查
performance_schema.data_locks,看看触发器是否因缺少索引把锁范围扩大了(比如UPDATE stats SET cnt = cnt + 1 WHERE type = NEW.type,而stats(type)没索引) - 千万别用
sysbench oltp_insert这种单表脚本压测——它不模拟真实字段约束和跨表校验,结果会严重乐观,上线直接翻车
查事务膨胀程度,重点盯WAL和回滚段
一个INSERT INTO orders VALUES (1),(2),(3)本来是一次写入搞定,但一旦挂上AFTER触发器,就变成了3次独立事务 + 3次binlog记录 + 3次锁申请。批量操作时WAL日志量直接翻倍,主从延迟跟着就上去了。这不仅仅是“多执行几条SQL”那么简单,而是事务生命周期被强行拉长,并行优化彻底废掉。
举个例子:用LOAD DATA INFILE导入50万行数据,理想情况是顺序大块写;但触发器一介入,等于执行50万次小事务,I/O瞬间随机化。pg_stat_bgwriter(PostgreSQL)或Innodb_os_log_written(MySQL)这些指标会异常凸起,一眼就能看出来。
- PostgreSQL:查
pg_stat_activity里backend_start与xact_start的时间差,如果远大于应用层预期,基本就是触发器拖住了事务 - MySQL:监控
Innodb_rows_updated和Handler_update的比值,如果接近1:1,说明每一行UPDATE都触发了额外操作 - 绝对禁止在触发器里做
SELECT COUNT(*) FROM big_table WHERE user_id = OLD.user_id——哪怕只查一行,没索引就是全表扫,每改一行就扫一遍,性能直接崩
识别隐式递归和锁等待链
触发器最危险的地方不是慢,而是不可见的连锁反应。比如用户表UPDATE触发器去更新统计表,统计表的UPDATE又触发另一个触发器去写日志表——这可不是简单的两层嵌套,而是调用链爆炸。线上偶发的死锁、卡顿,90%查不到源头,因为SHOW PROCESSLIST只显示主SQL,真正的阻塞源藏在触发器里。
容易踩的坑不少:IF UPDATE(status)没写,导致字段没变也硬执行触发逻辑;AFTER INSERT里又INSERT INTO同一张表,MySQL默认允许但极易引发无限递归;跨表操作没加NOLOCK或隔离级别不匹配,间隙锁直接锁住整段索引区间。
- SQL Server:设
RECURSIVE_TRIGGERS OFF,并在触发器开头加IF @@NESTLEVEL > 2 RETURN - MySQL:用
DECLARE EXIT HANDLER捕获异常,避免触发器失败导致整个事务静默回滚 - 所有数据库:触发器内涉及的JOIN/WHERE字段必须有索引,且类型严格匹配(
INT和BIGINT隐式转换会让索引失效)
绕过触发器做对比测试,别信“理论上不影响”
临时禁用触发器比任何理论分析都管用。PostgreSQL可以用ALTER TABLE t DISABLE TRIGGER ALL,MySQL设SQL_LOG_BIN=0(仅限从库维护),像listmonk这类系统甚至要改schema.sql把AFTER INSERT换成DEFERRED约束。实测100万条数据导入,启用触发器耗时58分19秒,禁用后18分33秒——不是“有点慢”,是三倍时间成本。
性能损耗不是固定百分比,它随数据量非线性放大:10万条时损耗168%,50万条升到220%,并发10进程时更冲到270%。这背后是锁竞争、CPU饱和、IO队列堆积的综合结果,不是调参能抹平的。
- listmonk的基准测试明确显示:触发器在批量导入时成为瓶颈,核心问题不在逻辑本身,而在
queries.sql里定义的锁机制与逐行触发的耦合 - MySQL压测反复验证:高并发下QPS跌破50%不是偶然,是BEFORE触发器内查询 + 锁等待 + 隐式递归链共同作用的结果
- 真正难处理的从来不是“要不要用触发器”,而是“有没有把触发逻辑推到应用层之后再回写数据库”——这点压测报告里不会体现,但上线后往往才是卡点
实际影响永远比预估更隐蔽:它不报错,不进慢日志,只悄悄吃掉你的吞吐、锁住你的连接、拖垮你的主从同步。评估时别只看单点,要盯住整个数据流链条里的锁、日志、事务和并发行为。
