mysql触发器无法直接查耗时,show profile和slow_query_log均无效;必须手动在触发器开头结尾插入now(6)打点写入诊断表,或通过禁用触发器对比前后dml耗时来定位性能瓶颈。

MySQL触发器里根本查不到耗时,别白费劲试SHOW PROFILE
SHOW PROFILE 和 SHOW PROFILES 对触发器完全无效——它只显示外层 INSERT/UPDATE/DELETE 的总耗时,触发器内部执行的 SQL 被彻底吞掉,不会单独计时、不会出现在 profile 列表里。你看到的“0.02 秒”,可能是主语句 0.005 秒 + 触发器跑了 0.015 秒,但你无从拆分。
常见错误现象:SHOW PROFILE FOR QUERY N 返回结果里没有触发器相关行;slow_query_log 只记录主 DML 语句,哪怕触发器里 SELECT COUNT(*) FROM huge_log_table 跑了 8 秒,日志里也只写 “INSERT INTO orders …” 一行,且 query_time 显示不到 1 秒。
- 触发器不是独立执行单元,它被内联进宿主语句执行,MySQL 不暴露其子执行上下文
-
performance_schema.events_statements_history_long能捕获触发器内语句,但需提前开启对应 instruments,且EVENT_NAME字段可能为statement/sql/select,不带 trigger 标识,靠SQL_TEXT模糊匹配风险高 - 别指望
INFORMATION_SCHEMA.INNODB_TRX查触发器事务 ID —— 禁用 autocommit 时,触发器和主语句共用一个TRX_ID,无法隔离
真想测耗时,只能在触发器里手动打点记 NOW(6)
最直接、最可靠的办法:在触发器开头和结尾各插一条时间戳写入诊断表。MySQL 的 NOW(6) 提供微秒级精度,差值可精确到毫秒级。
实操建议:
- 先建轻量诊断表:
CREATE TABLE trigger_debug_log (id BIGINT PRIMARY KEY AUTO_INCREMENT, trigger_name VARCHAR(64), start_time DATETIME(6), end_time DATETIME(6), duration_ms DECIMAL(10,3), conn_id INT) - BEFORE 触发器开头写:
INSERT INTO trigger_debug_log (trigger_name, start_time, conn_id) VALUES ('tr_orders_before_insert', NOW(6), CONNECTION_ID()) - AFTER 触发器结尾补耗时:
INSERT INTO trigger_debug_log (trigger_name, start_time, end_time, duration_ms, conn_id) SELECT 'tr_orders_after_insert', NOW(6), NOW(6), TIMESTAMPDIFF(MICROSECOND, start_time, NOW(6))/1000, CONNECTION_ID() FROM trigger_debug_log WHERE id = LAST_INSERT_ID() - 注意:不能用变量跨行传值(如
@start),因为触发器每次调用是独立作用域,@start在下一次触发时已被覆盖
禁用触发器对比耗时,才是最硬核的验证方式
当你怀疑某个触发器拖慢写入,最不可辩驳的证据就是关掉它再跑一次相同操作——尤其适合批量导入、定时任务等可复现场景。
MySQL 8.0.19+ 支持在线禁用:
ALTER TABLE orders DISABLE TRIGGER tr_orders_after_insert- 执行相同 DML,对比
SELECT ROW_COUNT(), @@last_insert_id和客户端真实耗时 - 恢复:
ALTER TABLE orders ENABLE TRIGGER tr_orders_after_insert - 注意:禁用后要确认业务逻辑是否允许(比如审计日志、状态同步类触发器关掉会丢数据)
如果关掉后耗时从 1200ms 降到 180ms,那基本可以锁定问题就在这个触发器里,再结合打点日志定位具体哪条语句卡住。
别在触发器里查同表或大表,这是性能雪崩最常见的起点
很多“慢触发器”其实不是逻辑复杂,而是写了不该写的 SQL。最典型的就是在 BEFORE INSERT 里 SELECT * FROM same_table WHERE x = NEW.y —— MySQL 直接报错 Can't update table 't' in stored function/trigger because it is already used...,或者隐式加锁导致并发阻塞。
- 避免在触发器中查被修改的同一张表(尤其是 BEFORE 时机)
- 避免
SELECT COUNT(*)、SELECT MAX(id)这类聚合操作,高并发下极易成为热点 - 必须查关联表?确保
WHERE条件字段有索引,且优先用 JOIN 替代子查询 - 更优解:把查表逻辑提到应用层,用变量传入触发器,比如
SET @ref_val := (SELECT val FROM ref WHERE id = ?),再在触发器里用@ref_val
真正难的不是怎么记耗时,而是怎么让触发器本身别成为瓶颈——打点只是照妖镜,重构才是解药。











