会,MySQL触发器本身不直接制造死锁,但极易成为死锁的放大器和触发点;因其在事务中执行并合并锁,涉及写操作时会隐式增加行锁请求,导致锁顺序失控、间隙锁升级及多事务争抢同一行而形成循环等待。

会,MySQL 触发器本身不直接“制造”死锁,但它极易成为死锁的**放大器和触发点**——尤其在高并发、多事务、无序加锁的场景下,一个看似简单的 AFTER INSERT 触发器,可能瞬间把两个事务拖进循环等待。
为什么触发器会让死锁更频繁?
触发器在事务上下文中执行,它持有的锁会和主事务合并;一旦触发逻辑涉及写操作(比如更新计数器表),就等于在原事务里“悄悄多加了一次行锁请求”。而开发者往往忽略这点,导致锁顺序失控。
- 多个触发器同时修改同一张汇总表(如
stats_counter)时,不同事务按不同顺序争抢同一行,极易形成「事务A锁住id=1 → 等id=2」、「事务B锁住id=2 → 等id=1」的环路 - 触发器内执行
SELECT ... FOR UPDATE或UPDATE时,若目标行无索引,InnoDB 可能升级为间隙锁(GAP LOCK)甚至临键锁(NEXT-KEY LOCK),锁住本不需要的范围 - INSERT 触发器中调用函数或子查询,若该查询扫描大量未命中索引的行,会扩大锁粒度,增加与其他事务冲突概率
典型死锁现场:计数器表 + 高频插入
这是最常见也最容易复现的场景:你建了一张 group_count 表存各 group 的用户数,再用三个触发器同步维护。当每秒几十个 INSERT INTO customers 并发进来时,死锁日志里常出现:
Deadlock found when trying to get lock; try restarting transaction
根本原因不是触发器写错了,而是所有触发器都试图 UPDATE group_count SET cnt = cnt + 1 WHERE group_id = ? —— 这条语句在 group_id 无索引时会锁全表;即使有索引,多个事务对同一 group_id 的并发更新也会因锁顺序/间隙锁叠加而卡住。
- ✅ 正确做法:确保
group_count.group_id是主键或唯一索引,且该字段在触发器中始终以确定顺序参与更新 - ⚠️ 常见错误:用
INSERT ... ON DUPLICATE KEY UPDATE替代UPDATE,但没配INSERT ... SELECT的显式锁提示,仍可能触发间隙锁竞争 - ? 更稳方案:把计数逻辑移到应用层,用 Redis 原子增减 + 定期落库,彻底绕开触发器锁链
怎么验证是不是触发器惹的祸?
别猜,直接看死锁日志(SHOW ENGINE INNODB STATUS\G 输出中的 LATEST DETECTED DEADLOCK 段)。重点抓三处:
- 看每个事务最后执行的 SQL —— 如果都是类似
UPDATE group_count SET ... WHERE group_id = 123,基本锁定是触发器引发的资源争抢 - 查事务持有哪些锁(
HELD LOCKS)和等待哪些锁(WAITING FOR THIS LOCK TO BE GRANTED),确认是否跨表(如主表 + 汇总表)形成锁依赖环 - 对比触发器定义:检查是否有嵌套调用(比如 A 触发器改了 X 表 → X 表也有触发器 → 又去改 Y 表),这种隐式调用链极难排查,但会直接导致循环依赖
真正棘手的从来不是“会不会死锁”,而是“为什么每次都是这条触发器报错,但单测又从不复现”——因为死锁只在特定并发时机、特定数据分布、特定锁等待序列下才爆发。上线前不做批量压测 + 死锁日志采集,等于裸奔。










