触发器本身不开启独立事务,但会让死锁排查变得隐蔽——你看到的是一条 INSERT 报错 Deadlock found when trying to get lock,背后可能已悄悄锁了三张表。

触发器本身不开启独立事务,但会让死锁排查变得隐蔽——你看到的是一条 INSERT 报错 Deadlock found when trying to get lock,背后可能已悄悄锁了三张表。关键不是“有没有触发器”,而是“它在锁什么、怎么锁的、谁和它撞上了”。
看 SHOW ENGINE INNODB STATUS\G 里有没有触发器痕迹
死锁发生后立刻执行该命令,重点盯住 LATEST DETECTED DEADLOCK 区块:
- 查
TRANSACTION段里是否出现mysql tables in use 2, locked 2—— 数字大于 1,说明不止主表,至少还有一张表被卷入,大概率是触发器写的日志表(如t_audit、t_log) - 比对
HOLDS THE LOCK(S)和WAITING FOR THIS LOCK TO BE GRANTED中的锁对象:如果其中一把锁落在t_order_history这类只在触发器里出现的表上,基本坐实是它带进来的 - 看 SQL 堆栈顺序:如果紧挨着
INSERT INTO t_order后面就出现UPDATE t_user SET last_order_time = ...,而你又确认t_order上定义了AFTER INSERT触发器,这就是铁证
用 sys.dm_tran_locks + sys.dm_exec_requests 追踪锁归属(SQL Server)
系统视图不会直接标出“这是触发器干的”,但能帮你交叉锁定嫌疑链:
- 从
sys.dm_tran_locks找出持有锁(request_status = 'GRANT')和等待锁(request_status = 'WAIT')的会话,比对它们共同锁住的表:主 SQL 只该碰t_order,但两个会话都锁了t_customer→ 立刻去查t_order的触发器是否更新了客户表 - 用
sys.dm_exec_requests查blocking_session_id非 0 的请求,再通过sql_handle反查原始语句:SELECT text FROM sys.dm_exec_sql_text(sql_handle)—— 如果语句是INSERT,但会话却持有了t_product_stock的排它锁,那触发器八成在里面做了减库存 - 别信
sys.dm_os_waiting_tasks:它只告诉你“等什么资源”,不告诉你“谁加的”。触发器的锁会合并到父会话下,容易误判成主 SQL 自己卡住了
检查触发器内 DML 是否走索引、是否引发间隙锁
触发器里一条没走索引的 UPDATE,在 RR 隔离级别下可能升级为全表间隙锁,直接引爆死锁:
- 对触发器里的每条
UPDATE或SELECT FOR UPDATE,用真实参数模拟执行,加EXPLAIN FORMAT=TRADITIONAL确认是否命中索引:type必须是const或ref,不能是ALL或index - 禁用范围条件:
WHERE status = 'pending'这种写法会触发 gap lock;统一改用主键或唯一索引更新,例如WHERE order_id = NEW.order_id - 避免子查询隐式锁表:触发器里写
SELECT balance FROM accounts WHERE user_id = NEW.user_id,InnoDB 可能提前对整个user_id索引区间加 gap lock —— 改成先在主事务开头用SELECT ... FOR UPDATE预锁对应行
为什么死锁日志里找不到触发器名字?
因为触发器不生成独立事务 ID,它的执行堆栈被折叠进主事务里。你查 sys.dm_exec_trigger_stats 时,execution_count 突增、total_elapsed_time 从 1ms 跳到 500ms,且时间点和死锁高峰重合,这才是最真实的触发器“作案信号”。真正危险的不是触发器存在,而是它让锁路径不可见——应用日志里只记了一条 INSERT,DBA 却要从死锁图 XML 的 executionStack 里手动翻出触发器名,这一步漏掉,排查就永远在原地打转。

















