mysql触发器会导致死锁吗_mysql并发风险说明

来源:这里教程网 时间:2026-02-28 20:36:32 作者:

会,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 表),这种隐式调用链极难排查,但会直接导致循环依赖

真正棘手的从来不是“会不会死锁”,而是“为什么每次都是这条触发器报错,但单测又从不复现”——因为死锁只在特定并发时机、特定数据分布、特定锁等待序列下才爆发。上线前不做批量压测 + 死锁日志采集,等于裸奔。

相关推荐