after触发器比before更容易引发死锁,因其在新行插入并加x锁后才执行,再对其他表或行执行dml会叠加锁请求、拉长等待链;而before仅修改new字段,不额外加锁(除非显式for update)。

触发器本身不直接死锁,但它会把主事务的锁链拉得更长、更乱,让死锁从“偶发”变成“高频”。
为什么AFTER触发器比BEFORE更容易引发死锁
AFTER触发器在新行已插入并加 X 锁之后才执行,此时再对其他表或行做 UPDATE/SELECT FOR UPDATE,等于在已有锁基础上叠加新锁请求——锁等待链直接变长。
- AFTER INSERT 中执行
UPDATE stats SET count = count + 1 WHERE type = 'order':若type无索引,InnoDB 全表扫描 + 加 Next-Key Lock,整张stats表瞬间被锁住 - BEFORE INSERT 只能改
NEW字段值,不额外加锁(除非你主动写SELECT ... FOR UPDATE) - 多级嵌套触发器(比如 AFTER → 触发 BEFORE UPDATE → 再触发另一个 AFTER)会让锁路径不可控,8.0+ 才能在
SHOW ENGINE INNODB STATUS中准确定位内层语句
触发器里哪些SQL最容易放大锁冲突
这些操作在高并发下几乎等于主动制造死锁热点,必须立刻砍掉或重构:
- 跨业务主表的
UPDATE,例如从orders触发器去改users余额表——一律移出触发器,改用应用层统一锁序 -
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
怎么确认死锁日志里是触发器惹的祸
别只扫一眼报错 SQL,重点看 SHOW ENGINE INNODB STATUS\G 输出中 LATEST DETECTED DEADLOCK 块里的两个事务:
- 找
UPDATE或SELECT FOR UPDATE语句是否出现在触发器目标表上(比如主表是orders,死锁链里却有UPDATE user_points SET ... WHERE user_id = ?) - 检查
holds the locks和waiting for this lock是否跨了不同表,且顺序相反(如事务 A 持有orders锁等待user_points,事务 B 持有user_points锁等待orders) - 若日志里出现类似
UPDATE order_log SET status = 'processed' WHERE order_id = NEW.id这种带NEW.的语句,基本可断定是AFTER INSERT触发器执行的
真正难处理的不是“有没有触发器”,而是它把锁逻辑藏在数据库层,让你看不到加锁顺序、测不出锁粒度、也很难在应用层做重试控制——所以排查时第一反应不该是“关掉它”,而是用 EXPLAIN 把触发器内部每条 SQL 的执行计划都跑一遍,看有没有 type: ALL 或 rows 估算严重偏离实际。











