确认慢update由触发器引起需交叉验证:测试库快而生产库慢(因触发器存在)、show engine innodb status显示lock wait但trx_query仅显主语句、performance_schema.data_lock_waits中阻塞事务在innodb_trx无活跃语句、应用日志显示批量操作激活多触发器;禁用应采用变量绕过法(set @disable_triggers = 1),而非删除或重命名;触发器内低效sql如无索引count、函数包裹字段、json解析等会被逐行放大,须单独explain分析。

慢UPDATE不是表本身的问题,而是触发器在后台同步执行低效SQL——它不报错、不显眼,但会让单行更新从几毫秒变成几百毫秒。
怎么确认慢UPDATE是触发器引起的
别只盯着主语句的EXPLAIN,它根本不会显示触发器里的SQL。真正有效的判断方式是交叉验证:
- 同一SQL在测试库快、生产库慢,且生产库有触发器而测试库没有
-
SHOW ENGINE INNODB STATUS里看到事务状态为LOCK WAIT,但trx_query只显示UPDATE your_table,而实际卡在对另一张表的写入(比如t_audit) - 用
performance_schema.data_lock_waits查等待链,发现BLOCKING_ENGINE_TRANSACTION_ID对应的事务在INNODB_TRX中查不到活跃语句——说明它早已执行完主SQL,正卡在触发器逻辑里 - 应用日志里该时间点有批量INSERT/UPDATE,而每个操作都会激活多个触发器(例如一次插入触发3个不同表的UPDATE)
如何安全地临时禁用触发器验证因果
MySQL没有DROP TRIGGER以外的原生禁用命令,直接删会破坏业务一致性。推荐用“变量绕过法”:
- 在触发器开头加判断:
IF @disable_triggers IS NOT NULL THEN LEAVE proc_label; END IF; - 压测或排查时执行:
SET @disable_triggers = 1;,之后再跑慢UPDATE,看是否恢复 - 不要用
RENAME TABLE隔离验证——容易漏切应用层配置,且无法覆盖跨表触发场景 - 禁用后必须立刻补日志或计数校验:比如触发器原本写
audit_log,禁用期间得人工补一条记录,否则后续数据对不上
触发器里哪些SQL最容易拖垮UPDATE
触发器内每行都执行一次,所以哪怕看起来简单的语句,也会被放大N倍:
-
SELECT COUNT(*) FROM orders WHERE user_id = NEW.user_id:如果orders.user_id没索引,就是全表扫描 × N行 -
UPDATE t_balance SET total = total + NEW.amount WHERE user_id = NEW.user_id:若t_balance.user_id无索引,会逐行加X锁,不是一行,是整张表 -
INSERT INTO log_table SELECT * FROM temp_cache WHERE ref_id = NEW.id:这种INSERT ... SELECT在触发器里极易持间隙锁,阻塞并发插入 -
json_extract(NEW.payload, '$.user.id'):每次都要完整解析JSON字符串,无法复用执行计划 -
WHERE UPPER(email) = UPPER(NEW.email):函数包裹字段,索引直接失效,type=ALL
为什么EXPLAIN看不出问题但实际很慢
因为EXPLAIN只分析顶层语句,触发器里的SQL完全不在其视野内。你看到主语句type=ref、key=idx_id,不代表快——真正卡住的是触发器里那句SELECT name FROM users WHERE id = NEW.user_id。这句必须单独拎出来跑EXPLAIN,重点看:
-
key是否为NULL -
rows是否远超预期(比如查单个用户却扫几千行) -
Extra是否出现Using where; Using index condition以外的内容 - 关联字段类型是否严格一致(
INTvsBIGINT会导致隐式转换,索引失效)
最常被忽略的是:触发器里看似只是取个字段,裹上函数就成性能黑洞;而这类开销,在慢查询日志里只会记成“主语句耗时”,不会拆解归属。











