SHOW ENGINE INNODB STATUS 看不到触发器SQL,因其被视作父事务一部分,锁信息归入外层事务ID,仅显示持锁/等待表而不体现触发器内操作。

触发器引发的锁不会出现在 SHOW ENGINE INNODB STATUS 的事务 SQL 中,必须靠交叉验证才能定位——它不报错、不显式加锁,却真实阻塞后续 UPDATE。
为什么 SHOW ENGINE INNODB STATUS 看不到触发器里的 SQL
MySQL 只记录客户端发起的顶层语句(比如 INSERT INTO t_order),而触发器内执行的 UPDATE t_log SET status = 'done' WHERE ref_id = NEW.id 被 InnoDB 视为父事务的一部分,锁信息统一归到外层事务 ID 下。日志里只显示“持有锁的表”和“等待锁的表”,不体现谁下的锁。
- 死锁日志中两个事务都只显示主表操作,看不出冲突点
-
INNODB_TRX里某事务trx_state是LOCK WAIT,但trx_query显示的是INSERT,实际卡住的是触发器里对另一张表的UPDATE - 用
performance_schema.data_lock_waits查到等待链,BLOCKING_ENGINE_TRANSACTION_ID对应的事务在INNODB_TRX里查不到trx_query——因为它早已执行完INSERT,正卡在触发器逻辑里
如何确认死锁或阻塞来自触发器
关键看三处线索是否形成闭环:
- 死锁日志中,两个事务的
HOLDS THE LOCK(S)和WAITING FOR THIS LOCK TO BE GRANTED涉及的表,是否包含触发器目标表(例如:t_order触发器更新t_audit,但日志里t_audit同时出现在 HOLD 和 WAIT 两侧) - 应用日志中,对应时间点是否有批量写入(如
INSERT INTO t_order VALUES (),(),()),且这些操作会激活多个触发器(一次插入触发 3 个不同表的UPDATE) -
SHOW CREATE TRIGGER trigger_name显示其内部含:UPDATE/DELETE、范围条件(WHERE created_at > DATE_SUB(NOW(), INTERVAL 1 DAY))、SELECT ... FOR UPDATE,或对无索引字段的更新(WHERE module = 'payment',而module无索引)
禁用触发器前必须验证的三个动作
直接 DROP TRIGGER 可能导致业务数据不一致。实操中应先做最小干预验证:
- 临时关闭触发器所在表的自动提交:
SET autocommit = 0,然后手动执行一条会触发它的INSERT,再用SELECT * FROM performance_schema.data_locks WHERE OBJECT_NAME = 't_audit'确认锁是否真由该触发器产生 - 往一个专用日志表
trigger_debug插入记录,包含NOW()、USER()、NEW.id等上下文,观察是否真被调用 - 临时把触发器改成
BEFORE+SET NEW.col =并故意设成非法值,观察是否报错,从而反推是否被触发
触发器里哪些操作最容易放大锁风险
不是所有触发器都会引发隐性锁问题,但以下几种模式几乎必然带来并发瓶颈:
-
UPDATE目标表本身是高频写入表(如统计表、日志表),容易与外部事务形成交叉锁 - 使用范围条件(
WHERE created_at > ...)触发 Gap Lock,在 RR 隔离级别下锁住间隙而非单行 - 级联触发:一张表的
AFTER INSERT触发器更新user_points,而user_points上又有BEFORE UPDATE触发器去写points_audit——锁路径不可控,死锁概率指数上升 -
WHERE条件字段无索引(如module = 'payment'),InnoDB 走聚簇索引全表扫描,对所有扫描行加 X 锁,哪怕最终只改一行
真正麻烦的不是触发器报错,而是它安静地把锁延长、扩大、藏进父事务里——你看到的永远只是“主表 INSERT 卡住了”,却找不到谁在锁那张“被触发的表”。排查时一定要跳出单条 SQL 的视角,盯住锁等待链、触发器定义、索引覆盖这三点交叉印证。


















