不能直接看到。SHOW ENGINE INNODB STATUS 的 LATEST DETECTED DEADLOCK 只显示顶层 SQL,不展开触发器内语句,需结合锁表闭环、应用日志、触发器定义及执行计划综合排查。

SHOW ENGINE INNODB STATUS 能看到触发器引发的死锁吗
不能直接看到。SHOW ENGINE INNODB STATUS 输出的 LATEST DETECTED DEADLOCK 区块只展示最终参与死锁的「顶层 SQL」(即客户端发起的 INSERT/UPDATE/DELETE),不会展开触发器内执行的语句。即使死锁由触发器里的 UPDATE t_log SET status = 'done' WHERE ref_id = NEW.id 引起,日志里也只会显示触发它的那条 INSERT INTO t_order ... —— 你得靠推理补全链路。
如何确认死锁是否来自触发器
关键看三处线索是否闭环:
- 死锁日志中两个事务的「持有锁」和「等待锁」涉及的表,是否包含触发器目标表(比如
t_order触发器更新t_audit,但日志里t_audit出现在 HOLD 和 WAIT 两侧) - 应用层日志中,对应时间点是否有批量插入/更新操作,且这些操作恰好会激活多个触发器(例如一次
INSERT INTO t_order带来 3 个触发器,分别更新t_log、t_stat、t_cache) - 检查触发器定义:
SHOW CREATE TRIGGER trigger_name,确认其内部 SQL 是否含多表更新、范围条件、未走索引的 WHERE(如WHERE user_name LIKE '%abc%')或SELECT ... FOR UPDATE
特别注意:MySQL 触发器在父事务上下文中执行,它加的锁不会单独开启新事务,但会延长原事务持锁时间——这正是隐性死锁的温床。
排查触发器 SQL 的索引与执行顺序
触发器内 SQL 若没走索引,极易升级为间隙锁或全表扫描锁,大幅提高死锁概率。实操步骤如下:
- 对触发器中的每条
UPDATE/DELETE/SELECT ... FOR UPDATE单独执行EXPLAIN,重点看type是否为ALL或index,key是否为NULL - 若触发器更新多张表(如先改
t_user再改t_profile),检查其他业务逻辑是否以相反顺序访问这两张表(比如另一个事务先UPDATE t_profile再UPDATE t_user) - 避免在触发器中使用范围查询:如
UPDATE t_history SET flag = 1 WHERE created_at > DATE_SUB(NOW(), INTERVAL 1 DAY)会锁住整个时间范围的间隙,高并发下极易冲突
示例:一个 BEFORE INSERT ON t_payment 触发器里有 UPDATE t_balance SET total = total + NEW.amount WHERE user_id = NEW.user_id —— 如果 t_balance.user_id 没索引,这条语句就会对全表聚簇索引逐行加 X 锁,哪怕只改一行。
临时禁用触发器验证死锁是否消失
这是最直接的因果验证方式,但仅限非生产环境或低峰期:
- 禁用单个触发器:
ALTER TABLE t_order DISABLE TRIGGER tr_after_insert_order(MySQL 5.7.2+ 支持) - 若版本不支持,可临时重命名触发器:
DROP TRIGGER IF EXISTS tr_after_insert_order_off;CREATE TRIGGER tr_after_insert_order_off ...(把原逻辑注释掉) - 压测复现原场景,观察
SHOW ENGINE INNODB STATUS是否不再出现该死锁模式
注意:禁用后需同步检查业务一致性逻辑是否被破坏(比如积分未累加、状态未同步),这类问题往往比死锁更难定位。
触发器带来的死锁最难缠的地方在于:它藏在主 SQL 后面,不显式出现在调用栈里,且锁行为完全依赖内部 SQL 的执行计划。一旦发现死锁反复涉及某张表,优先查它的触发器,而不是一上来就改应用层重试逻辑。


















