触发器中调用SELECT会拖慢写入,因其被纳入当前事务并扩大锁范围,尤其全表扫描或无索引查询时延迟骤增;嵌套触发器和OLD/NEW误用还会引发死锁与额外开销。

触发器里调用 SELECT 为什么会让写入变慢
因为 MySQL 在执行 INSERT/UPDATE 时,会把触发器里的 SELECT 当作“当前事务的一部分”,不仅加锁范围扩大,还可能阻塞其他并发写入。尤其当 SELECT 涉及未命中索引的表扫描,或跨库/跨表关联时,延迟直接从毫秒级跳到百毫秒以上。
- 别在
BEFORE触发器里查大表——哪怕只是SELECT COUNT(*) FROM orders WHERE user_id = NEW.user_id,也容易拖垮主键插入路径 - 如果必须查,确保被查字段有覆盖索引;用
EXPLAIN验证执行计划是否走了type=ref或更好 -
AFTER触发器里查数据虽不阻塞主 SQL,但会延长事务总耗时,影响 binlog 写入节奏和从库回放延迟
嵌套触发器如何意外放大执行次数
MySQL 允许触发器内修改另一张表,从而激活它的触发器——这就是隐式嵌套。问题在于:你只写了 1 条 INSERT,实际可能触发 5 层、20 次触发器调用,且每层都带自己的锁和日志开销。
- 检查
information_schema.TRIGGERS表,确认没有 A 表触发器改 B 表、B 表触发器又改 A 表的循环链 - 禁用嵌套最简单的方法是设
SET SESSION sql_mode = 'STRICT_TRANS_TABLES';不起作用;真正有效的是在触发器开头加IF @trigger_nesting_level IS NULL THEN SET @trigger_nesting_level = 1; ELSE LEAVE proc_label; END IF; - MySQL 8.0+ 可用
performance_schema.events_statements_history_long查看单条语句触发了多少次触发器事件,按EVENT_NAME过滤statement/sql/select和statement/com/Trigger
OLD 和 NEW 引用不当引发的锁等待
在 BEFORE UPDATE 里对 OLD 字段做子查询,比如 (SELECT name FROM users WHERE id = OLD.user_id),MySQL 会为该行加 S 锁(共享锁);而同一事务中若已有 UPDATE 对该行加了 X 锁,就会死锁或超时。
-
OLD只能读,不能用于触发新 DML;NEW可改,但改了也不会自动同步到原表——它只是当前触发器上下文里的副本 - 避免在触发器里用
OLD.xxx去 JOIN 其他表;改用提前在主 SQL 中把所需字段SELECT ... INTO @var传进来 - 开启
innodb_print_all_deadlocks = ON后,错误日志里会出现类似*** (1) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id ...的线索,重点看 lock_mode 和 lock_trx_id
怎么确认某次慢写入真是触发器导致的
不能只看慢日志里那条 INSERT 耗时高——得证明触发器本身在其中占了多少时间。MySQL 没有原生触发器耗时统计,得靠组合手段定位。
- 临时禁用触发器:
ALTER TABLE t1 DISABLE TRIGGER tr1;(MySQL 8.0.19+),对比开关前后的INSERTp95 耗时变化 - 在触发器第一行加
INSERT INTO debug_log VALUES (NOW(3), 'tr1_start');,最后一行再记一次,用TIMEDIFF算差值;注意别让 debug 表本身也带触发器 - 用
pt-query-digest分析慢日志时,加上--filter '$event->{arg} =~ m/trigger/i'提取所有触发器相关事件,再按Query_time排序
触发器性能问题最难缠的地方不是它多慢,而是它藏在主 SQL 下面,不深挖执行树根本看不到那一层额外的锁和 IO —— 很多人优化了主表索引,却漏掉了触发器里那个没走索引的 SELECT。


















