cpu飙升主因是触发器在dml时同步执行大量计算型操作,如子查询、join、函数调用和跨表更新,全压在同一事务中;error 1442是mysql主动拦截而非死循环,但会引发锁等待与线程堆积。

CPU飙升不是因为触发器“写得多”,而是它在每次DML时同步执行了太多计算型操作——子查询、JOIN、函数调用、跨表更新,全被压在一个事务里硬扛。
为什么ERROR 1442不是死循环,但会让CPU飙到300%?
报错 ERROR 1442 (HY000): Can't update table 'xxx' in stored function/trigger 是MySQL主动拦截,不是真循环。但它背后往往藏着更危险的逻辑:触发器里写了 UPDATE t SET x = NOW() WHERE id = NEW.id 这类语句,MySQL会先执行主SQL(已持锁),再进触发器尝试二次加锁——失败后回滚重试,或卡在锁等待上,线程堆积,CPU自然拉满。
- 别信“加了IF判断就安全”:只要触发器体里出现对本表的任何DML(哪怕带WHERE),就会触发拦截
- 客户端可能静默吞掉这个错误,只看到数据没写进去,却查不到日志
-
SHOW PROCESSLIST里如果看到大量线程停在Updating或Waiting for table metadata lock,基本就是它
怎么快速确认是不是递归触发在捣鬼?
递归本身不报错,但会让一次INSERT实际执行几十次嵌套查询,CPU在单事务内反复解析、优化、执行,开销爆炸。
- 查当前递归深度:
SELECT @@max_sp_recursion_depth;,值为0表示不限制(最危险) - 临时限制(会话级):
SET max_sp_recursion_depth = 1;,能强制暴露隐藏的递归链 - 没有直接命令看调用栈,但
SHOW PROCESSLIST的info字段常能看到正在执行的触发器语句,再结合INFORMATION_SCHEMA.TRIGGERS反查定义
真正管用的修复动作,不是删触发器,而是砍逻辑+挪位置
触发器只该干三件事:校验字段、改NEW.col值、写无关表的日志。所有需要扫描、聚合、远程调用、跨表更新的逻辑,必须移走。
- 删掉触发器里的
SELECT * FROM other_table WHERE x = NEW.id—— 改成应用层查好ID后传入;如必须查,确保other_table有覆盖WHERE的联合索引 - 禁用自定义函数调用,尤其含SQL的函数;
DETERMINISTIC函数也慎用,MySQL有时误判开销 - 用
SET NEW.updated_at = NOW()替代UPDATE t SET updated_at = NOW() WHERE id = NEW.id,前者不额外加锁 - 批量INSERT逐行触发,性能雪崩——业务允许的话,改成应用层合并逻辑,用单条
UPDATE ... CASE WHEN替代多个触发器更新
长事务+触发器=CPU和锁双爆雷
一个长事务里执行100次带触发器的UPDATE,触发器每次又去查日志表、更新统计表……这些操作全锁在同一个事务里,不仅CPU高,还会拖慢提交,阻塞其他连接。
-
SHOW ENGINE INNODB STATUS\G里若看到大量waiting for table metadata lock或upgrading lock,大概率是它 - 触发器里任何跨表UPDATE(比如从
orders去改users余额),一律移出,改用应用层统一锁序 - WHERE条件没走主键或唯一索引的DML,比如
UPDATE config SET value = 'on' WHERE module = 'payment',若module无索引,就是全表X锁
最易被忽略的点:触发器没报错,不代表它没在拖垮系统。很多问题发生在“静默失败”或“锁等待超时”之后,CPU已经烧了半小时,你还在查慢查询日志——得先看SHOW PROCESSLIST和INNODB STATUS,再动手。











