触发器本身不直接导致死锁,但因其隐式执行、共享事务id和锁生命周期,使单条sql变为复合锁操作,显著增加死锁概率;排查需从死锁现场反推,重点分析show engine innodb status中的连续堆栈、多表锁定及日志表锁争用。

触发器本身不直接导致死锁,但它在主事务中隐式执行、共享同一事务ID和锁生命周期,会让单条SQL变成“主语句 + 多条触发逻辑”的复合锁操作;只要触发器里含UPDATE、SELECT FOR UPDATE或跨表DML,死锁概率就陡增——排查必须从死锁现场反推,而不是先翻触发器代码。
怎么看SHOW ENGINE INNODB STATUS里有没有触发器参与
死锁发生后立刻执行SHOW ENGINE INNODB STATUS\G,重点盯LATEST DETECTED DEADLOCK区块里的三处线索:
- 找类似
INSERT INTO orders后紧跟UPDATE audit_log SET status = 'done' WHERE order_id = NEW.id的连续堆栈——这就是触发器执行痕迹,NEW.id是典型标识 - 看
TRANSACTION段中mysql tables in use 2, locked 2:数字大于1表明涉及多表,大概率有触发器介入 - 比对
HOLDS THE LOCK(S)和WAITING FOR THIS LOCK TO BE GRANTED中的表名,若其中一把锁落在t_log、audit_history这类日志表上,基本坐实是触发器行为
为什么sys.dm_exec_trigger_stats查不到死锁线索
这个DMV只统计触发器执行次数,完全不记录锁行为。它告诉你“触发器跑过”,但不告诉你“它当时锁了哪几行、持有多久、走的是哪个索引”。真正要挖的,是触发器实际运行时的资源等待路径:
- 死锁图XML中的
executionStack里出现trg_after_update_order这类名字,才是触发器卷入的铁证 - 如果触发器里调用了存储过程,
executionStack可能只显示外层,得结合sys.dm_exec_sql_text手动反查sql_handle对应的完整语句 -
SET STATISTICS XML ON开启后跑一次触发器对应的操作,看执行计划里有没有Key Lookup或Table Scan——这些意味着锁范围被意外放大
怎么验证触发器内DML是否走了索引
触发器里一条没走索引的UPDATE product_stock SET qty = qty - 1 WHERE sku_code = 'ABC123',在RR隔离级别下可能升级为全表间隙锁;而另一事务按相反顺序操作,ABBA锁序瞬间形成。验证必须实操:
- 用真实参数模拟执行
EXPLAIN FORMAT=TRADITIONAL SELECT * FROM product_stock WHERE sku_code = 'ABC123',确认type是const或ref,不能是ALL或index - 检查
key字段非NULL,且rows估算值接近实际影响行数;若Using where; Using index condition缺失,立刻补索引 - 禁用
WHERE status = 'pending'这类范围条件——它会触发gap lock;统一用主键或唯一索引更新
真正危险的不是触发器存在,而是它让锁路径变得不可见——你很难从应用日志里看出某次INSERT背后悄悄锁了三张表。排查时别只盯错误码1205,得进死锁图XML里找executionStack里的触发器名;优化时也别幻想靠改隔离级别解决,关键在让每条触发逻辑的锁粒度、顺序、索引都可预测、可收敛。











