触发器不开启新事务,而是隐式延长主事务锁生命周期,导致跨表abba锁序、未索引引发全表锁、伪表逐行处理放大竞争,且隔离级别调整无效。

触发器在事务内隐式延长锁生命周期
触发器不开启新事务,它完全运行在主事务上下文中。只要主事务没 COMMIT 或 ROLLBACK,触发器里所有 UPDATE、INSERT、SELECT FOR UPDATE 产生的锁就一直挂着。你看到的是一条 UPDATE orders,背后可能已悄悄给 user_points、audit_log、stats_summary 同时加了 X 锁——这些锁要等整个事务结束才释放。
常见错误现象:Deadlock found when trying to get lock 报错中,堆栈显示的 SQL 往往是触发器内部那句 UPDATE audit_log SET ... WHERE order_id = NEW.id,但应用日志只记了主表操作,排查时容易漏掉这一环。
跨表更新顺序不可控,天然形成 ABBA 锁序
当触发器更新另一张表,而这张表上又有自己的触发器(比如 orders → AFTER INSERT → user_points → BEFORE UPDATE → points_audit),锁路径就从单跳变成多跳。InnoDB 和 SQL Server 都不会为触发器重排加锁顺序,只按实际执行流加锁。
多个并发请求进入后,极易出现:线程 A 先锁 user_points 再等 points_audit,线程 B 反过来先锁 points_audit 再等 user_points。这就是典型的 ABBA 死锁链。
使用场景中高发于:订单插入触发积分更新,积分表变更又触发审计写入,同时还有后台任务直接更新积分表——三股力量交叉争夺同一行资源。
未走索引导致锁范围爆炸
触发器里一句看似普通的 UPDATE config SET value = 'on' WHERE module = 'payment',如果 module 字段没索引,InnoDB 就得全表扫描聚簇索引,并对每行都加 X 锁。哪怕最终只改一行,也等于锁住了整张表的活跃数据段。
检查方法必须实操:
- 对触发器中每条 DML,用真实参数模拟执行 EXPLAIN FORMAT=TRADITIONAL
- 确认 type 不是 ALL 或 index
- 确认 key 字段非 NULL,且 rows 估算值接近实际影响行数
- 若发现 Using where; Using index condition 缺失,立刻补索引
SQL Server 中伪表误用放大锁竞争
把 inserted 当成单行变量处理,是高频死锁源头之一。例如:
SELECT @id = id FROM inserted; -- ❌ 只取第一行,其余丢弃<br>UPDATE t2 SET status = 'done' WHERE id = @id;
这会导致:10 万行批量更新,触发器被调用 10 万次,每次只处理一行,锁反复申请释放;正确做法是集合运算:
UPDATE t2 SET status = 'done'<br>FROM t2 INNER JOIN inserted i ON t2.id = i.id;
若需聚合(如累加子项金额),必须 GROUP BY t2.id 并显式列出所有被更新列,否则 SQL Server 会报错或产生非预期结果。
真正难缠的不是“要不要用触发器”,而是它一旦嵌套、一旦没索引、一旦逐行处理,锁就会从点扩散成面,从毫秒拉长到秒级,且极难通过调隔离级别缓解——因为问题不在读,而在写锁的隐式叠加与顺序失控。











