是,触发器跨表更新总卡住是因为其复用主事务上下文,锁延迟释放且加锁顺序依赖执行流,易因A→B与B→A并发形成死锁;必须强制唯一索引访问、固定锁序、禁用非主键查询。

触发器跨表更新为什么总卡住
因为 MySQL 不会为触发器新开事务,所有操作都塞进主事务里——你 INSERT 一张表,触发器 UPDATE 另一张表,这两把锁得等到整个事务提交才释放。更麻烦的是,InnoDB 加锁顺序完全按执行流走,A → B 和 B → A 并发一碰就死锁。
常见错误现象:Deadlock found when trying to get lock 报错里只显示 INSERT INTO orders,但真正卡在 UPDATE user_points SET balance = ... 这句触发器 SQL 上;或者 SHOW PROCESSLIST 显示状态是 updating,INNODB_TRX 里却查不到等待记录——这是隐式锁在作祟。
所有跨表 DML 必须走唯一索引 + 固定顺序
触发器里每条 UPDATE、INSERT、SELECT FOR UPDATE 都得确认是否命中主键或唯一索引,否则容易升级成全表扫描+全段加锁。
- 用
EXPLAIN FORMAT=TRADITIONAL跑一遍触发器里的语句,重点看type字段:不是const或eq_ref就危险 - 禁用
WHERE status = 'pending'这类范围条件——它会触发间隙锁,高并发下锁一片 - 多表更新必须约定死顺序:比如所有触发器和应用代码都先锁
users,再锁user_logs,不能一个写users → user_logs,另一个反着来 -
SELECT FOR UPDATE必须带ORDER BY primary_key,否则扫描顺序不一致,ABBA 死锁直接复现
别让触发器查配置表或做状态判断
看似只是读一行配置,但没加锁的 SELECT 在 MVCC 下是快照读;一旦触发器里紧跟着 UPDATE,前面的 SELECT 就可能被升级为加锁读——而且如果配置字段没索引,整张表都可能被锁住。
- 固定值优先硬编码,或用
SELECT ... INTO @var FROM DUAL,别查业务表 - 状态流转判断(比如“是否已处理”)不能放触发器里:
BEFORE UPDATE查 + SIGNAL 的组合在并发下必然双写,根本不可靠 - 真要联动,改用应用层事务控制:先
SELECT FOR UPDATE预锁目标行,再统一做变更,锁顺序可控 - 日志类写入尽量异步:改成插入轻量缓冲表(如
log_buffer),由定时任务 flush,绕过触发器同步阻塞
怎么确认问题出在触发器而不是主 SQL
SHOW ENGINE INNODB STATUS\G 是唯一能直接抓到线索的地方,但得会看。
- 关注
LATEST DETECTED DEADLOCK区块里mysql tables in use 2, locked 2—— 数字大于 1 基本就是触发器惹的祸 - 比对
HOLDS THE LOCK(S)和WAITING FOR THIS LOCK TO BE GRANTED,如果一把锁来自audit_log或stats_summary这类明显是触发器目标表,基本坐实 -
TRANSACTIONS段里找lock_mode X locks rec but not gap出现在非主表上,再结合堆栈里INSERT INTO orders后紧跟UPDATE audit_log,就是触发器执行痕迹 - 别依赖
INNODB_TRX查锁等待:隐式锁阶段它根本不记录,等你看到 WAITING 时,往往已经卡了几十秒
真正难缠的不是语法写错,而是锁范围在毫秒级动态膨胀——一条没索引的 UPDATE 在触发器里跑一次,就能让后续所有对那张表的写入排队。动手前,先 EXPLAIN,再决定要不要留它。

















