触发器在高并发下引发行锁争用,本质是将应用层应控的锁行为隐入数据库事务:BEFORE触发器仅改NEW字段不加新锁,AFTER触发器在主语句持锁后执行DML,延长锁时长且易因缺失索引导致Next-Key Lock;其内SELECT隐式加gap lock,主键查询仍锁索引页,MySQL 8.0+锁更严;跨表DML易致ABBA死锁,须禁用跨主表更新、强制覆盖索引、禁用显式锁读,并将配置类读操作移至应用缓存。

触发器在高并发下引发行锁争用,根本不是“写法不够好”的问题,而是它把本该由应用层控制的锁行为,悄悄塞进了数据库事务内部——你根本看不到加锁顺序,也测不出锁粒度。
BEFORE 和 AFTER 触发器的锁行为差异极大
BEFORE 触发器只允许修改 NEW 字段,不执行额外 SQL,也就不会申请新锁;AFTER 触发器则不同,它在主语句已持锁(比如对 orders 行加了 X 锁)之后才运行,此时再执行 UPDATE order_summary SET total = total + NEW.amount,等于在同一事务里叠加锁请求。
- AFTER 中任何
UPDATE/SELECT FOR UPDATE都会延长锁持有时间,且锁范围可能远超预期 - 若
order_summary上WHERE条件没走索引,InnoDB 会加 Next-Key Lock,锁住整个索引区间 - 多个事务同时插入订单,AFTER 触发器并发更新同一汇总行,立刻形成排队队列
触发器里查表操作会隐式加 gap lock
哪怕只是 SELECT name FROM staff WHERE id = NEW.operator_id,在 REPEATABLE READ 隔离级别下,InnoDB 不仅锁匹配行,还会锁前后间隙。并发日志写入时,所有事务都在等同一段索引页上的 gap lock,TPS 断崖下跌。
- 即使
staff.id是主键,该 SELECT 仍会锁定对应索引页,高并发下页成热点 - MySQL 8.0+ 对触发器内
SELECT的锁更严格,5.7 能跑通的逻辑,在 8.0 可能直接报 deadlock - 用
EXPLAIN检查触发器内每条语句:如果type是ALL或index,说明正在全表/全索引扫描,锁爆炸风险极高
跨表 DML 是死锁高频发生点
死锁日志里一旦出现类似 *** (1) WAITING FOR THIS LOCK TO BE GRANTED: TABLE: `inventory`,基本可断定是触发器越界了——主事务锁 orders,触发器去锁 inventory,另一条路径反着来,ABBA 循环瞬间成立。
- 禁止在触发器里执行跨业务主表的
UPDATE,比如从orders触发器去改users余额表 - 所有读操作必须命中覆盖索引:
EXPLAIN SELECT config_value FROM config_table WHERE key_name = 'order_timeout'的type必须是const或ref - 彻底禁用
SELECT FOR UPDATE和SELECT LOCK IN SHARE MODE;配置类数据优先缓存到应用层
真正难处理的不是“要不要用触发器”,而是它把锁逻辑藏在数据库层,让你既看不到加锁顺序、也测不出锁粒度、还很难在应用层做重试控制——一旦线上出现锁等待堆积,排查成本远高于提前剥离逻辑。


















