触发器性能优化需重点关注索引缺失、存储过程调用、嵌套更新、排序聚合及执行计划误判五大雷区。应为where/join/order by字段建合适索引,避免隐式全表扫描;慎用存储过程,优先将逻辑提回触发器本体或显式指定索引;防止递归触发与锁等待;避免using temporary/filesort;结合explain与实际负载综合评估。

触发器里用了 SELECT 却没加索引
触发器里隐式扫描全表是最常见的性能雷区。比如在 INSERT 触发器中写 SELECT id FROM orders WHERE user_id = NEW.user_id,而 orders.user_id 没建索引,每次插入都触发一次全表扫描。
- 用
EXPLAIN直接执行触发器内 SQL,看type是否为ALL或index - 对触发器中所有
WHERE、JOIN、ORDER BY涉及的字段,检查是否都有对应索引 - 注意复合索引顺序:如果触发器查的是
WHERE status = 'pending' AND created_at > NOW() - INTERVAL 1 DAY,(status, created_at)比单列索引有效得多
触发器调用存储过程导致执行计划失效
MySQL 5.7+ 对存储过程内 SQL 的执行计划缓存不敏感,尤其是带参数的动态查询。触发器里调用 CALL update_user_stats(NEW.user_id),里面又做 SELECT ... FROM logs WHERE user_id = p_user_id,优化器可能反复生成低效计划。
- 把关键逻辑从存储过程“提”回触发器本体(前提是逻辑不复用),让
EXPLAIN能真实反映执行路径 - 若必须用存储过程,在过程开头加
/*+ QB_NAME(main) */(MySQL 8.0.22+)或用FORCE INDEX显式指定索引 - 避免在存储过程中拼接 SQL 字符串——预编译失效,执行计划无法复用
触发器中嵌套 UPDATE 引发二次触发与锁等待
比如在 users 表的 AFTER UPDATE 触发器里更新 user_profiles,而后者也有触发器再更新 users,形成隐式递归;更隐蔽的是,两个触发器同时更新同一行,造成 Lock wait timeout exceeded。
- 用
SHOW PROCESSLIST查看长时间运行的Waiting for table metadata lock或Updating状态线程 - 在触发器开头加
IF NOT (@in_trigger := @in_trigger + 1) THEN LEAVE; END IF;防止意外递归(需先设@in_trigger := 0) - 把跨表更新逻辑移到应用层异步处理,或改用
INSERT ... ON DUPLICATE KEY UPDATE减少锁持有时间
执行计划里出现 Using temporary / Using filesort
触发器内哪怕只有一条 GROUP BY 或 ORDER BY,只要没走索引覆盖,就会触发临时表和文件排序——这在高并发 INSERT 场景下会迅速拖垮吞吐。
- 用
EXPLAIN FORMAT=TRADITIONAL查Extra字段,确认是否含Using temporary或Using filesort - 确保
ORDER BY字段在索引最右位,且无函数包裹(如ORDER BY DATE(created_at)会跳过索引) - 触发器中尽量避免
GROUP BY;真要聚合,优先用COUNT(*)这类能走索引统计的写法,而非COUNT(column)
执行计划不是万能的——它只反映单次语句,而触发器的真实开销还藏在事务上下文、锁粒度和 binlog 写入节奏里。别只盯着 rows 小就放心,key_len 偏大、filtered 过低、或者 Extra 里反复出现 Using index condition 都是信号。最麻烦的是那些“看起来没问题”的触发器,跑在批量导入时才暴露出问题。










