触发器强制同步逐行执行,将批量INSERT拆为N次串行事务,引发高开销与锁争用;空触发器单次也有0.1–0.3ms固定延迟,跨表查询、索引失效或写多索引日志表会显著拖慢TPS。

因为触发器强制同步逐行执行,把本应并发、批量的 INSERT 拆成 N 次串行事务操作——这不是配置能绕开的机制限制,而是 MySQL 的底层行为。
触发器让 INSERT 变成“N 次单行事务”
MySQL 对 INSERT INTO t VALUES (), (), () 这类语句,不会整体触发一次,而是对每一行调用一次触发器。10 万行 = 10 万次触发器上下文切换 + 权限检查 + SQL 解析 + 执行逻辑。哪怕触发器只有一行 SET NEW.updated_at = NOW(),也意味着 10 万次系统时钟调用和事务锁持有。
- 空触发器也有 0.1–0.3ms 固定开销,无法通过
SET SQL_LOG_BIN = 0跳过 -
SHOW PROCESSLIST中大量线程卡在Updating或Sending data,实际是等触发器里的SELECT或INSERT完成 - 慢查询日志里同一条
INSERT反复出现,每次耗时稳定在几毫秒以上,说明开销来自触发器本身,而非主语句
触发器内最常拖垮吞吐的三类操作
真正吃掉 TPS 的不是触发器语法,而是它在事务内同步干了不该干的事:
-
BEFORE INSERT里查大表:比如SELECT balance FROM accounts WHERE user_id = NEW.user_id,若accounts.user_id没索引,就是全表扫描 + 行锁,其他插入全被阻塞 - WHERE 条件导致索引失效:如
UPPER(email) = UPPER(NEW.email),每次插入都扫全表 -
AFTER INSERT里写日志表:若日志表用 InnoDB + 多个二级索引,每行插入都要维护 B+ 树结构,innodb_row_lock_waits明显上升
怎么确认真是触发器在拖慢 INSERT
别只看慢日志里那条 INSERT 耗时高,得定位到触发器本身的执行时间:
- 确保
performance_schema开启:UPDATE performance_schema.setup_consumers SET ENABLED = 'YES' WHERE NAME IN ('events_statements_history', 'events_stages_history') - 执行插入后查:
SELECT EVENT_NAME, TIMER_WAIT FROM performance_schema.events_stages_history WHERE NESTING_EVENT_ID IN (SELECT EVENT_ID FROM performance_schema.events_statements_history WHERE SQL_TEXT LIKE '%INSERT INTO your_table%') ORDER BY TIMER_WAIT DESC LIMIT 5 -
TIMER_WAIT单位是皮秒,除以10^12才是秒数;若看到statement/sql/select或statement/sql/insert占比高,基本就是触发器里的 SQL 在吃时间
禁用比优化更有效,且必须分清同步与异步边界
触发器的设计定位是“轻量、确定、单行响应”,不是业务协调中枢。花时间给触发器加索引、拆函数、缓存结果,本质是方向错了。
- 必须保同步的,只留最轻逻辑:
SET NEW.created_at = NOW()、SIGNAL校验 - 所有跨表查询、更新统计、写审计日志、发通知,一律移出触发器——改由应用层异步发 MQ,或监听 binlog(如 canal)消费
- 真要保留中转动作,用极简表:
INSERT INTO trigger_queue (id, table_name) VALUES (NEW.id, 'orders'),引擎选MEMORY或精简InnoDB,只建必要索引
复杂点在于:很多团队误以为“只要触发器逻辑写得够短,就能扛住批量”,但问题不在长短,而在它被强制塞进事务、无法并行、锁范围不可控——这才是吞吐断崖式下跌的根源。

















