MySQL触发器会使单条INSERT延迟增加0.2–5ms,高并发下延迟可能翻3倍以上;应避免跨表查询、禁止更新原表、使用ROW格式binlog,并优先考虑应用层替代方案。

MySQL触发器会让INSERT变慢多少
在每秒上千次写入的业务里,加一个触发器往往会让单条INSERT延迟增加 0.2–5ms,具体取决于触发器逻辑复杂度和是否涉及SELECT或UPDATE。这不是线性增长——当并发写入从100 QPS升到2000 QPS时,延迟可能翻3倍以上,因为锁竞争和事务日志压力同步放大。
实操建议:
- 用
SYSBENCH或自建压测脚本对比「有触发器」和「注释掉触发器逻辑后」的INSERTp95延迟,别只看平均值 - 触发器里避免
SELECT ... FROM other_table,尤其不能查大表或没索引的字段;查缓存表(如config_cache)也得加WHERE id = NEW.x_id这种强过滤 - 如果只是记录日志,优先用
INSERT INTO audit_log (...) VALUES (NEW.id, NOW(), USER()),别调用存储过程或函数
触发器导致主从延迟飙升的典型场景
主库上触发器执行UPDATE order_status SET updated_at = NOW()这类语句,会生成额外binlog事件;从库必须串行回放这些事件,一旦触发器多、更新频繁,Seconds_Behind_Master就容易卡在几十秒甚至几分钟。
常见错误现象:
- 主库
SHOW PROCESSLIST看着很空,但从库Replication_IO_Running: Yes但Replication_SQL_Running: Yes下Seconds_Behind_Master持续上涨 - 从库
SHOW ENGINE INNODB STATUS里看到大量Waiting for table metadata lock,根源常是触发器更新了同一张表
实操建议:
- 触发器里禁止更新触发它的那张表(比如
orders的AFTER INSERT里再UPDATE orders),MySQL 8.0+会报ERROR 1442 (HY000): Can't update table 'orders' in stored function/trigger - 把跨表更新逻辑拆出去,改用应用层异步发MQ,或者定时任务补数据
- 检查
binlog_format是不是ROW(必须是),MIXED或STATEMENT会让触发器行为不可预测
触发器与事务一致性的隐含冲突
触发器运行在父事务上下文中,它抛出的异常(比如INSERT违反唯一键)会直接让整个事务回滚——这点常被忽略,尤其是当触发器里调用INSERT INTO log_table却没给log_table加足够索引时,容易因锁等待超时引发连锁回滚。
使用场景提醒:
- 审计类触发器(只
INSERT日志)相对安全;计数类(UPDATE counter SET val = val + 1)风险高,因为要加行锁 -
BEFORE触发器修改NEW.xxx字段会影响后续SQL;AFTER触发器看不到父SQL的锁释放时机,容易撞上LOCK WAIT TIMEOUT - MySQL 5.7对触发器嵌套深度限制是16层,实际中两个互相调用的触发器就可能触达上限,报错
ERROR 1456 (HY000): Recursive limit 16 reached
替代触发器的更可控方案
真要保一致性又扛高写入,触发器不是首选。应用层控制粒度更细,运维也更容易定位问题。
可选路径:
- 用
INSERT ... ON DUPLICATE KEY UPDATE代替BEFORE INSERT做防重或默认值填充 - 关键状态变更走
UPDATE ... WHERE version = ?+ 应用层CAS重试,比触发器里查旧值再更新更可靠 - 需要强审计?直接开启
general_log并过滤INSERT/UPDATE/DELETE语句,或用mysqlbinlog解析row格式binlog——虽然要自己解析,但不拖慢写入
最麻烦的其实是调试:触发器出错不会直接返回给客户端,只会记在error_log里,而且没有调用栈。上线前务必在测试环境用INSERT故意触发各种边界值,盯着tail -f /var/log/mysql/error.log看有没有ERROR 1418或ERROR 1305这类提示。











