触发器必然拖慢insert,因其在事务内同步阻塞执行;实测含insert或select的触发器可使1000行插入耗时从80ms增至650ms;应通过performance_schema定位瓶颈,砍除非必要逻辑或将耗时操作移出事务。

触发器必然拖慢 INSERT,这不是配置能调的“慢”
只要触发器里有 SELECT、INSERT、UPDATE 或调用函数,单条 INSERT 就必须等它跑完才能返回。实测中,一个含 INSERT INTO log_table 的 BEFORE INSERT 触发器能让批量插入 1000 行耗时从 80ms 增至 320ms;若再加一次 SELECT COUNT(*),直接跳到 650ms。这不是“优化空间”,而是同步阻塞的硬性开销。
查 performance_schema 定位真实瓶颈,别只看慢查询日志
慢查询日志里那条 INSERT 耗时高,不等于它本身慢——可能是触发器在后台吃时间。MySQL 没原生 Profiler,但 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占比高,基本就是触发器里的SELECT在拖后腿
触发器里最常踩的性能坑
90% 的慢不是语法错,而是它在事务里干了不该干的事:
-
BEFORE INSERT里写SELECT balance FROM accounts WHERE user_id = NEW.user_id,而accounts.user_id没索引 → 全表扫描 + 行锁,其他插入全被堵住 -
WHERE UPPER(email) = UPPER(NEW.email)→ 索引失效,每次插入都扫全表 -
AFTER INSERT里UPDATE order_summary SET total = total + NEW.amount→ 和主表行锁竞争,MySQL 8.0+ 更容易报Deadlock found when trying to get lock - 触发器里调用存储过程或自定义函数 → 每次调用都带上下文切换和隐式子事务,批量插入时开销指数放大
真正有效的修复路径:砍逻辑 or 移出事务
不是“优化触发器”,而是重新划清同步与异步边界:
- 必须保同步的,只留最轻量逻辑:
SET NEW.created_at = NOW()、IF NEW.amount - 所有跨表查询、写日志、更新统计、发通知、调外部 API,一律移出触发器 —— 改由应用层异步发 MQ,或监听
binlog(用canal/maxwell)消费 - 真要保留中转动作,用极简
INSERT INTO trigger_queue (id, table_name) VALUES (NEW.id, 'orders'),这张表引擎选MEMORY或精简InnoDB,只建必要索引
复杂点在于:很多人把“业务逻辑必须强一致”等同于“必须在触发器里做”,其实多数场景下,应用层事务包裹主表写 + 日志表写,比触发器更可控、更容易批量合并、也更易监控。最容易被忽略的是——触发器里每多一行 SQL,就多一次事务内上下文切换,这在高并发下不是线性增长,而是雪球效应。











