确认触发器引发死锁需重点分析SHOW ENGINE INNODB STATUS\G中LATEST DETECTED DEADLOCK区块:检查两事务是否跨表反向加锁(如A持orders锁等user_points、B持user_points锁等orders),并查找含NEW.或OLD.的SQL(如UPDATE order_log SET status='processed' WHERE order_id=NEW.id),若触发器对非主业务表执行DML且未走索引或含范围锁,则极可能为根因。

怎么从死锁日志确认是触发器在堵路
别只扫一眼报错 SQL,重点看 SHOW ENGINE INNODB STATUS\G 输出中 LATEST DETECTED DEADLOCK 区块的两个事务细节:
- 检查
WAITING FOR THIS LOCK TO BE GRANTED和HOLDS THE LOCK(S)是否跨了不同表,且加锁顺序相反(比如事务 A 持有orders锁、等待user_points;事务 B 持有user_points、等待orders)——这是触发器引发反向加锁链的典型特征 - 找有没有含
NEW.或OLD.的语句,例如UPDATE order_log SET status = 'processed' WHERE order_id = NEW.id,基本可断定出自AFTER INSERT触发器 - 若触发器目标表(如
points_log)反复出现在HOLDS和WAITING两侧,而主业务表(如orders)只出现一次,说明锁热点已偏移至触发器逻辑
为什么AFTER触发器更容易卡住整个事务
AFTER INSERT 执行时,新行已落盘并持有行级 X 锁,事务尚未提交;此时再执行其他 DML,等于在已有锁基础上叠加新锁请求:
-
AFTER INSERT中执行UPDATE stats SET count = count + 1 WHERE type = 'order':若type字段无索引,InnoDB 会全表扫描并加 Next-Key Lock,瞬间锁住整张统计表 -
BEFORE INSERT只能修改NEW字段值,不产生额外 DML,也就不会引入新锁(除非你主动写SELECT ... FOR UPDATE) - MySQL 8.0+ 才能在
INNODB STATUS中准确定位多级嵌套触发器里的内层语句;低版本里看到的“阻塞 SQL”往往只是最外层,真正卡点藏在触发器里
查不到blocking_query?试试这个三步联动法
当 INNODB_LOCK_WAITS 的 blocking_trx_id 对应的 trx_query 为空、PROCESSLIST.INFO 也为空时,大概率是触发器在后台静默执行:
- 先查
SELECT * FROM information_schema.INNODB_TRX WHERE trx_state = 'RUNNING' AND trx_started < DATE_SUB(NOW(), INTERVAL 60 SECOND),找出长时间运行却没显式 SQL 的事务 - 用该事务的
trx_mysql_thread_id去查performance_schema.events_statements_history(需提前开启consumers),过滤SQL_TEXT LIKE '%NEW.%' OR SQL_TEXT LIKE '%OLD.%' - 结合
information_schema.TRIGGERS查出该线程可能触发的触发器定义:SELECT EVENT_OBJECT_TABLE, ACTION_TIMING, EVENT_MANIPULATION FROM information_schema.TRIGGERS WHERE EVENT_OBJECT_SCHEMA = 'your_db'
触发器里哪些操作必须立刻砍掉
以下行为在并发稍高时,几乎必然导致锁等待超时甚至死锁,排查到就该立即下线或重构:
- 跨业务主表的
UPDATE,例如从orders触发器里去改users表余额——一律移出触发器,改用应用层统一锁序(如先锁user,再锁order) -
WHERE条件未命中主键或唯一索引的 DML,比如UPDATE config SET value = 'on' WHERE module = 'payment',若module无索引,就是全表 X 锁 - 带范围条件的
SELECT ... FOR UPDATE,如SELECT * FROM logs WHERE created_at > DATE_SUB(NOW(), INTERVAL 1 DAY) FOR UPDATE,在 RR 隔离下会锁住整个时间区间 - 调用存储过程,除非该过程明确声明只读、无任何 DML,否则锁路径不可控
真正卡点往往不在触发器语法本身,而在它隐式延长了锁持有时间、打乱了加锁顺序、又缺乏索引支撑——这些比调参重要得多。


















