是,触发器会显著拖慢INSERT/UPDATE/delete。因其在事务内同步串行执行,叠加select无索引、批量调用函数、跨表写入无索引等操作,性能损耗远超预期。

触发器执行会拖慢 INSERT/UPDATE/DELETE 吗
会,而且往往比预期更明显。mysql 触发器在语句执行的同一事务上下文中同步运行,不是异步或后台任务。这意味着 INSERT 语句必须等 BEforE 触发器执行完、再执行原操作、再等 AFTER 触发器结束,整个链路串行阻塞。
常见被低估的开销来源:
-
SELECT查询(尤其没走索引或跨表 JOIN)会显著放大延迟 - 调用
UUID()、NOW()等函数本身开销小,但若在每行触发器中重复调用(如批量插入 1000 行),累积效应明显 - 写入另一张表时若目标表缺乏主键或索引,可能引发全表锁或 insert buffer 溢出
- 触发器里执行存储过程,而该过程含事务控制(
START TRANSACTION)会导致报错Can't execute statement in a READ ONLY transaction
哪些场景下触发器性能问题最突出
不是所有触发器都一样慢,关键看它干了什么。以下场景极易成为瓶颈:
- 批量导入:用
LOAD DATA INFILE或INSERT ... VALUES (...), (...), (...)插入数百行以上时,每个值都会单独触发一次触发器逻辑 - 高频更新热点行:比如订单表中
status字段频繁更新,且触发器每次都要往日志表写一条记录并SELECT count(*)统计,I/O 和锁竞争会陡增 - 触发器内调用
INSERT INTO ... SELECT ... FROM other_table:若other_table很大且无合适 WHERE 条件,等于每次 DML 都带一次全表扫描 - 使用
NEW.col LIKE '%xxx%'做条件判断:无法利用索引,字符串匹配在每行都执行,CPU 负载直线上升
替代触发器的低开销方案有哪些
多数业务逻辑其实不需要强一致性地绑定在 DML 上,可把实时性要求降一级,换更可控的方式:
- 应用层统一收口:把原本放在触发器里的字段填充、状态派生、审计日志等逻辑,移到 ORM 的
save()或 DAO 层,配合数据库连接复用和批量操作减少 round-trip - 异步消息解耦:DML 成功后发一条 kafka / rabbitmq 消息,由独立消费者处理后续动作(如更新搜索索引、发送通知),避免数据库线程被长耗时任务卡住
- 定时聚合补算:对“统计类”需求(如每日 UV、每小时订单量),放弃实时更新,改用
Event定时执行INSERT INTO summary_table SELECT ... GROUP BY,既稳定又易监控 - 物化视图思路(MySQL 8.0+):用
CREATE VIEW+ 应用层缓存,或借助Generated column存储计算字段(如full_name VARCHAR(100) AS (CONCAT(first_name, ' ', last_name)) STORED),避免运行时反复拼接
真要保留触发器时怎么压测和调优
别只看单条 SQL 执行时间。触发器的真实压力藏在并发和批量行为里:
- 用
sysbench或自建脚本模拟真实负载:例如 50 线程并发执行INSERT INTO t1 VALUES (1),(2),(3)...,观察SHOW PROCESSLIST中是否大量出现Waiting for table level lock或Updating状态 - 开启
performance_schema并查events_statements_history_long,过滤event_name LIKE '%trigger%',看平均执行时间、是否频繁发生wait/io/file/innodb/innodb_data_file - 触发器内禁止写日志文件(如
SELECT ... INTO OUTFILE)、禁止调用SLEEP()、禁止访问外部服务(http 请求等)——这些在 MySQL 线程中是灾难性的 - 如果必须跨表写入,确保目标表有主键 + 写入字段上有索引,且 INSERT 使用
INSERT IGNORE或ON DUPLICATE KEY UPDATE替代先查后插模式
触发器不是不能用,而是它的“透明性”容易掩盖资源争用。上线前不压测并发批量场景,等于埋了个定时性能炸弹。