触发器必然拖慢insert,因其在事务内同步执行查询、更新等操作;应检查定义、用explain分析其sql、避免锁表与全表扫描,并可通过变量临时禁用。

触发器让 INSERT 变慢,先看它到底执行了什么
MySQL 触发器在 INSERT 时同步执行,只要触发器里有查询、更新、函数调用或跨表操作,就会拖慢主语句。不是“可能慢”,是“必然多一次(甚至多次)事务内执行”。
实操建议:
- 用
SHOW CREATE TRIGGER trigger_name看触发器定义,重点检查是否含SELECT、UPDATE、INSERT INTO ... SELECT或调用UUID()/NOW()等非确定性函数 - 在测试库开
general_log,复现一次插入,直接看日志里实际执行了哪些语句——常会发现触发器悄悄查了三张表 - 触发器中避免访问大表,尤其不能出现没索引的
WHERE条件,否则INSERT会等全表扫描结束
EXPLAIN 不显示触发器,但能看透它的代价
EXPLAIN 对主 INSERT 语句本身有效,但不会展开触发器逻辑。不过,你可以把触发器里的 SQL 单独拎出来 EXPLAIN,这才是关键。
常见错误现象:主语句 EXPLAIN 显示 type: ALL,但表明明有索引——其实是触发器里某条 SELECT 没走索引,而你只看了主语句。
实操建议:
- 把触发器中每一条独立 SQL(尤其是
SELECT和UPDATE)单独执行EXPLAIN,注意key和rows字段 - 如果触发器用了
SELECT ... FROM t1 JOIN t2,确保两个表的关联字段都有索引,且类型严格一致(比如INT和BIGINT关联会失效) - 对触发器内频繁访问的字段,宁可加冗余索引,也不要依赖联合索引的最左前缀——触发器 SQL 很难写得足够精准去命中
触发器里用 INSERT ... SELECT 最容易锁表
这是线上最常踩的坑:INSERT INTO log_table SELECT ... FROM main_table WHERE ... 这类语句在触发器中执行,会持有一个范围锁(gap lock),阻塞其他事务的插入,同时自身也容易被别的事务堵住。
使用场景:比如记录操作日志、同步计数器、生成关联单号——这些完全可以用异步方式替代,没必要卡在事务里。
实操建议:
- 把这类写操作从触发器里移出,改用应用层发 MQ 或写入轻量队列表(
INSERT DELAYED已废弃,不推荐) - 真要保一致性,至少改成
INSERT INTO ... VALUES (...)静态值,避免SELECT带来的锁和执行计划不确定性 - 如果必须保留,确认目标表有主键或唯一索引,否则 MySQL 会升级为表级锁
触发器性能差,别急着删,先关掉看看
临时禁用比重写安全得多。MySQL 不支持 DISABLE TRIGGER,但可以用变量+条件绕过。
实操建议:
- 在触发器开头加判断:
IF @disable_triggers IS NULL THEN ... END IF;,需要压测或维护时,先执行SET @disable_triggers = 1; - 用
information_schema.TRIGGERS查触发器状态,配合监控看TRIGGER_NAME和EVENT_MANIPULATION是否高频触发 - 注意:触发器中调用存储函数也会继承主语句的上下文锁,哪怕函数只是返回一个常量,也要检查其定义是否隐式访问了表
复杂点在于,触发器的执行时间不体现在慢查询日志的 Query_time 里,而是摊在主语句头上;更麻烦的是,它没法被 pt-query-digest 单独归类——你得靠日志拆解+人工匹配才能定位到那一行真正拖后腿的 SELECT。











